/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2010-2015 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 org.glassfish.jersey.server.oauth1; import java.security.Principal; 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.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.Provider; import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap; /** Default in-memory implementation of OAuth1Provider. 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 * @author Miroslav Fuksa */ @Provider public class DefaultOAuth1Provider implements OAuth1Provider { private static final ConcurrentHashMap<String, Consumer> consumerByConsumerKey = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Token> accessTokenByTokenString = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Token> requestTokenByTokenString = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, String> verifierByTokenString = new ConcurrentHashMap<>(); @Override public Consumer getConsumer(final String consumerKey) { return consumerByConsumerKey.get(consumerKey); } /** * Register 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 {@link Consumer} object for the newly registered consumer. */ public Consumer registerConsumer(final String owner, final MultivaluedMap<String, String> attributes) { return registerConsumer(owner, newUUIDString(), newUUIDString(), attributes); } /** * Register a new consumer configured with Consumer Key. * * @param owner Identifier of the owner that registers the consumer (user ID or similar). * @param key Consumer key. * @param secret Consumer key secret. * @param attributes Additional attributes (name-values pairs - to store additional * information about the consumer, such as name, URI, description, etc.) * @return {@link Consumer} object for the newly registered consumer. */ public Consumer registerConsumer(final String owner, final String key, final String secret, final MultivaluedMap<String, String> attributes) { final Consumer c = new Consumer(key, secret, 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(final String owner) { final Set<Consumer> result = new HashSet<>(); for (final 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(final String principalName) { final Set<Token> tokens = new HashSet<>(); for (final 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(final Token token, final Principal userPrincipal, final Set<String> roles) { final Token authorized = token.authorize(userPrincipal, roles); requestTokenByTokenString.put(token.getToken(), authorized); final 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(final String token, final String principalName) { final 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() { final String tmp = UUID.randomUUID().toString(); return tmp.replaceAll("-", ""); } @Override public Token getRequestToken(final String token) { return requestTokenByTokenString.get(token); } @Override public OAuth1Token newRequestToken(final String consumerKey, final String callbackUrl, final Map<String, List<String>> attributes) { final Token rt = new Token(newUUIDString(), newUUIDString(), consumerKey, callbackUrl, attributes); requestTokenByTokenString.put(rt.getToken(), rt); return rt; } @Override public OAuth1Token newAccessToken(final OAuth1Token requestToken, final String verifier) { if (verifier == null || requestToken == null || !verifier.equals(verifierByTokenString.remove(requestToken.getToken()))) { return null; } final Token token = requestTokenByTokenString.remove(requestToken.getToken()); if (token == null) { return null; } final Token at = new Token(newUUIDString(), newUUIDString(), token); accessTokenByTokenString.put(at.getToken(), at); return at; } public void addAccessToken(final String token, final String secret, final String consumerKey, final String callbackUrl, final Principal principal, final Set<String> roles, final MultivaluedMap<String, String> attributes) { final Token accessToken = new Token(token, secret, consumerKey, callbackUrl, principal, roles, attributes); accessTokenByTokenString.put(accessToken.getToken(), accessToken); } @Override public OAuth1Token getAccessToken(final String token) { return accessTokenByTokenString.get(token); } /** Simple read-only implementation of {@link OAuth1Consumer}. */ public static class Consumer implements OAuth1Consumer { private final String key; private final String secret; private final String owner; private final MultivaluedMap<String, String> attributes; private Consumer(final String key, final String secret, final String owner, final Map<String, List<String>> attributes) { this.key = key; this.secret = secret; this.owner = owner; this.attributes = getImmutableMap(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 attributes; } @Override public Principal getPrincipal() { return null; } @Override public boolean isInRole(final String role) { return false; } } private static MultivaluedMap<String, String> getImmutableMap(final Map<String, List<String>> map) { final MultivaluedHashMap<String, String> newMap = new MultivaluedHashMap<>(); for (final Map.Entry<String, List<String>> entry : map.entrySet()) { newMap.put(entry.getKey(), entry.getValue()); } return newMap; } /** Simple immutable implementation of {@link OAuth1Token}. * */ public class Token implements OAuth1Token { 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(final String token, final String secret, final String consumerKey, final String callbackUrl, final Principal principal, final Set<String> roles, final 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(final String token, final String secret, final String consumerKey, final String callbackUrl, final Map<String, List<String>> attributes) { this(token, secret, consumerKey, callbackUrl, null, Collections.<String>emptySet(), new ImmutableMultivaluedMap<>(getImmutableMap(attributes))); } public Token(final String token, final String secret, final Token requestToken) { this(token, secret, requestToken.getConsumer().getKey(), null, requestToken.principal, requestToken.roles, ImmutableMultivaluedMap.<String, String>empty()); } @Override public String getToken() { return token; } @Override public String getSecret() { return secret; } @Override public OAuth1Consumer getConsumer() { return DefaultOAuth1Provider.this.getConsumer(consumerKey); } @Override public MultivaluedMap<String, String> getAttributes() { return attribs; } @Override public Principal getPrincipal() { return principal; } @Override public boolean isInRole(final 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(final Principal principal, final Set<String> roles) { return new Token(token, secret, consumerKey, callbackUrl, principal, roles == null ? Collections.<String>emptySet() : new HashSet<>(roles), attribs); } } }