/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * http://glassfish.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.jersey.oauth.server.api.providers; import com.sun.jersey.core.util.MultivaluedMapImpl; import com.sun.jersey.oauth.server.spi.OAuthConsumer; import com.sun.jersey.oauth.server.spi.OAuthProvider; import com.sun.jersey.oauth.server.spi.OAuthToken; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.Provider; /** Default in-memory implementation of OAuthProvider. Stores consumers and tokens * in static hash maps. Provides some additional helper methods for consumer * and token management (registering new consumers, retrieving a list of all * registered consumers per owner, listing the authorized tokens per principal, * revoking tokens, etc.) * * @author Martin Matula */ @Provider public class DefaultOAuthProvider implements OAuthProvider { private static final ConcurrentHashMap<String, Consumer> consumerByConsumerKey = new ConcurrentHashMap<String, Consumer>(); private static final ConcurrentHashMap<String, Token> accessTokenByTokenString = new ConcurrentHashMap<String, Token>(); private static final ConcurrentHashMap<String, Token> requestTokenByTokenString = new ConcurrentHashMap<String, Token>(); private static final ConcurrentHashMap<String, String> verifierByTokenString = new ConcurrentHashMap<String, String>(); @Override public Consumer getConsumer(String consumerKey) { return consumerByConsumerKey.get(consumerKey); } /** Registers a new consumer. * * @param owner Identifier of the owner that registers the consumer (user ID or similar). * @param attributes Additional attributes (name-values pairs - to store additional * information about the consumer, such as name, URI, description, etc.) * @return Consumer object for the newly registered consumer. */ public Consumer registerConsumer(String owner, MultivaluedMap<String, String> attributes) { Consumer c = new Consumer(newUUIDString(), newUUIDString(), owner, attributes); consumerByConsumerKey.put(c.getKey(), c); return c; } /** Returns a set of consumers registered by a given owner. * * @param owner Identifier of the owner that registered the consumers to be retrieved. * @return consumers registered by the owner. */ public Set<Consumer> getConsumers(String owner) { Set<Consumer> result = new HashSet<Consumer>(); for (Consumer consumer : consumerByConsumerKey.values()) { if (consumer.getOwner().equals(owner)) { result.add(consumer); } } return result; } /** Returns a list of access tokens authorized with the supplied principal name. * * @param principalName Principal name for which to retrieve the authorized tokens. * @return authorized access tokens. */ public Set<Token> getAccessTokens(String principalName) { Set<Token> tokens = new HashSet<Token>(); for (Token token : accessTokenByTokenString.values()) { if (principalName.equals(token.getPrincipal().getName())) { tokens.add(token); } } return tokens; } /** Authorizes a request token for given principal and roles and returns * verifier. * * @param token Request token to authorize. * @param userPrincipal User principal to authorize the token for. * @param roles Set of roles to authorize the token for. * @return OAuth verifier value for exchanging this token for an access token. */ public String authorizeToken(Token token, Principal userPrincipal, Set<String> roles) { Token authorized = token.authorize(userPrincipal, roles); requestTokenByTokenString.put(token.getToken(), authorized); String verifier = newUUIDString(); verifierByTokenString.put(token.getToken(), verifier); return verifier; } /** Checks if the supplied token is authorized for a given principal name * and if so, revokes the authorization. * * @param token Access token to revoke the authorization for. * @param principalName Principal name the token is currently authorized for. */ public void revokeAccessToken(String token, String principalName) { Token t = (Token) getAccessToken(token); if (t != null && t.getPrincipal().getName().equals(principalName)) { accessTokenByTokenString.remove(token); } } /** Generates a new non-guessable random string (used for token/customer * strings, secrets and verifier. * * @return Random UUID string. */ protected String newUUIDString() { String tmp = UUID.randomUUID().toString(); return tmp.replaceAll("-", ""); } @Override public Token getRequestToken(String token) { return requestTokenByTokenString.get(token); } @Override public OAuthToken newRequestToken(String consumerKey, String callbackUrl, Map<String, List<String>> attributes) { Token rt = new Token(newUUIDString(), newUUIDString(), consumerKey, callbackUrl, attributes); requestTokenByTokenString.put(rt.getToken(), rt); return rt; } @Override public OAuthToken newAccessToken(OAuthToken requestToken, String verifier) { if (verifier == null || !verifier.equals(verifierByTokenString.remove(requestToken.getToken()))) { return null; } Token token = requestToken == null ? null : requestTokenByTokenString.remove(requestToken.getToken()); if (token == null) { return null; } Token at = new Token(newUUIDString(), newUUIDString(), token); accessTokenByTokenString.put(at.getToken(), at); return at; } @Override public OAuthToken getAccessToken(String token) { return accessTokenByTokenString.get(token); } /** Simple read-only implementation of {@link OAuthConsumer}. */ public static class Consumer implements OAuthConsumer { private final String key; private final String secret; private final String owner; private final MultivaluedMap<String, String> attribs; private Consumer(String key, String secret, String owner, Map<String, List<String>> attributes) { this.key = key; this.secret = secret; this.owner = owner; this.attribs = newImmutableMultiMap(attributes); } @Override public String getKey() { return key; } @Override public String getSecret() { return secret; } /** Returns identifier of owner of this consumer - i.e. who registered * the consumer. * * @return consumer owner */ public String getOwner() { return owner; } /** Returns additional attributes associated with the consumer (e.g. name, * URI, description, etc.) * * @return name-values pairs of additional attributes */ public MultivaluedMap<String, String> getAttributes() { return attribs; } @Override public Principal getPrincipal() { return null; } @Override public boolean isInRole(String role) { return false; } } /** Simple immutable implementation of {@link OAuthToken}. * */ public class Token implements OAuthToken { private final String token; private final String secret; private final String consumerKey; private final String callbackUrl; private final Principal principal; private final Set<String> roles; private final MultivaluedMap<String, String> attribs; protected Token(String token, String secret, String consumerKey, String callbackUrl, Principal principal, Set<String> roles, MultivaluedMap<String, String> attributes) { this.token = token; this.secret = secret; this.consumerKey = consumerKey; this.callbackUrl = callbackUrl; this.principal = principal; this.roles = roles; this.attribs = attributes; } public Token(String token, String secret, String consumerKey, String callbackUrl, Map<String, List<String>> attributes) { this(token, secret, consumerKey, callbackUrl, null, Collections.<String>emptySet(), newImmutableMultiMap(attributes)); } public Token(String token, String secret, Token requestToken) { this(token, secret, requestToken.getConsumer().getKey(), null, requestToken.principal, requestToken.roles, ImmutableMultiMap.EMPTY); } @Override public String getToken() { return token; } @Override public String getSecret() { return secret; } @Override public OAuthConsumer getConsumer() { return DefaultOAuthProvider.this.getConsumer(consumerKey); } @Override public MultivaluedMap<String, String> getAttributes() { return attribs; } @Override public Principal getPrincipal() { return principal; } @Override public boolean isInRole(String role) { return roles.contains(role); } /** Returns callback URL for this token (applicable just to request tokens) * * @return callback url */ public String getCallbackUrl() { return callbackUrl; } /** Authorizes this token - i.e. generates a clone with principal and roles set * to the passed values. * * @param principal Principal to add to the token. * @param roles Roles to add to the token. * @return Cloned token with the principal and roles set. */ protected Token authorize(Principal principal, Set<String> roles) { return new Token(token, secret, consumerKey, callbackUrl, principal, roles == null ? Collections.<String>emptySet() : new HashSet<String>(roles), attribs); } } protected static MultivaluedMap<String, String> newImmutableMultiMap(Map<String, List<String>> source) { if (source == null) { return ImmutableMultiMap.EMPTY; } return new ImmutableMultiMap(source); } private static class ImmutableMultiMap extends MultivaluedMapImpl { public static final ImmutableMultiMap EMPTY = new ImmutableMultiMap(); private ImmutableMultiMap() { } ImmutableMultiMap(Map<String, List<String>> source) { for (Map.Entry<String, List<String>> e : source.entrySet()) { super.put(e.getKey(), e.getValue() == null ? Collections.<String>emptyList() : Collections.unmodifiableList(new ArrayList<String>(e.getValue()))); } } @Override public List<String> put(String k, List<String> v) { throw new UnsupportedOperationException(); } @Override public Set<Entry<String, List<String>>> entrySet() { return Collections.unmodifiableSet(super.entrySet()); } @Override public Set<String> keySet() { return Collections.unmodifiableSet(super.keySet()); } @Override public List<String> remove(Object o) { throw new UnsupportedOperationException(); } @Override public void putAll(Map<? extends String, ? extends List<String>> map) { throw new UnsupportedOperationException(); } @Override public Collection<List<String>> values() { return Collections.unmodifiableCollection(super.values()); } } }