Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
package dk.portalprotect.sample.oauth2;

import java.io.IOException;
import java.util.Hashtable;
import java.util.Properties;
import java.util.UUID;

import dk.itp.caching.lru.LruCache;
import dk.itp.security.authentication.oauth.AbstractOAuth2AuthenticationPlugin;
import dk.itp.security.passticket.PTException;
import dk.itp.security.passticket.User;
import dk.itp.security.passticket.server.AuthErrorCodes;
import dk.itp.security.utils.Base64;
import dk.portalprotect.oauth.OAuth2Helper;

/**
 * Demonstration OAuth2 plugin - not suitable for production use.
 * It issues a new ticket by generating a random key and returning it as the grantticket. It then persists the issuing
 * users ticket and saves it in a memory cache for up to an hour - during that our, the grant ticket gets access to the
 * previously saved session contents.
 * 
 * Please note that a production OAuth plugin must take care to validate the provided client_id and ensure that it
 * already exists in a known registry - it needs to ensure that the client id is valid for the attempted operation, 
 * especially it needs to ensure that implicity grant is only used if explicitly allowed.
 * 
 * Also, the provided scope must be validated for content which has meaning to the application, and the application
 * needs to use this information when authorizing a request and allowing access to data on the users behalf.
 *  
 * @author Kim Rasmussen
 * @version $Revision$
 *
 * <pre>
 * PortalProtect - Security infrastructure
 * Copyright(c) 2014, Asseco Denmark A/S, All rights reserved.Ceptor
 * 
 * This source code is confidential.
 * </pre>
 */
public class DemoOAuth2AuthenticationPlugin extends AbstractOAuth2AuthenticationPlugin {
	private LruCache cache = new LruCache();
	private LruCache authCache = new LruCache();
	
	private class AuthCacheEntry {
		String bearertoken;
		String clientid;
		String clientSecret = "password";
		String redirectUri;
	}
	
	public DemoOAuth2AuthenticationPlugin() {
		cache.setCacheSize(1000);
		cache.setTimeout(60000*60); // 1 hour
		cache.setForceTimeout(60000*60); // 1 hour

		authCache.setCacheSize(1000);
		authCache.setTimeout(120000); // 2 minutes
		authCache.setForceTimeout(120000); // 2 minutes
	}
	
	@Override
	protected Properties validateAuthRequest(User user, Properties input) throws PTException {
		Properties p = super.validateAuthRequest(user, input);
		
		// Validate that client_id / redirect_uri is valid
		// Could also check if state or other input parameters are valid
		
		return p;
	}

	@Override
	protected Properties handleTokenRequest(User user, Properties input) throws PTException {
		Properties p = validateTokenRequest(user, input);

		// Exchange authorization_code with a grant token
		
		if (OAuth2Helper.AUTHORIZATION_CODE.equals(input.getProperty(OAuth2Helper.GRANT_TYPE))) {
			String authorizationCode = input.getProperty(OAuth2Helper.CODE);
			
			AuthCacheEntry entry = (AuthCacheEntry) authCache.get(authorizationCode);
			
			if (entry == null || !entry.clientid.equals(input.getProperty(OAuth2Helper.CLIENT_ID )) || !input.getProperty(OAuth2Helper.CLIENT_SECRET).equals(entry.clientSecret)) {
				throw new PTException("Invalid authorization_code, client ID or client secret", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid authorization_code, client_id or client_secret");				
			}
			if (!entry.redirectUri.equals(input.getProperty(OAuth2Helper.REDIRECT_URI ))) {
				throw new PTException("Invalid redirect URI", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid redirect URI");				
			}

			p.setProperty(OAuth2Helper.ACCESS_TOKEN, entry.bearertoken);
			p.setProperty(OAuth2Helper.TOKEN_TYPE, "Bearer");
			p.setProperty(OAuth2Helper.EXPIRES_IN, "3600");
			
			// Here, we could also add a refresh_token if we had one to add.
		} else { // Must be refresh_token, since validateTokenRequest ensures nothing else can pass
			@SuppressWarnings("unused")
			String refreshToken = input.getProperty(OAuth2Helper.REFRESH_TOKEN);
		
			throw new PTException("Refresh tokens are not supported at the moment", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "refresh_token not supported");
		}
		
		return p;
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	protected java.util.Properties handleAuthRequest(dk.itp.security.passticket.User user, java.util.Properties input) throws PTException {
		Properties p = validateAuthRequest(user, input);
		
		String clientid = input.getProperty(OAuth2Helper.CLIENT_ID);

		// Create a new clone and use this to avoid altering the current users original session
		User newUser = (User) user.clone();
		if (newUser.stateVariables == null)
			newUser.stateVariables = new Hashtable();
						
		// Save the requested scope for later use when the session is restored
		newUser.stateVariables.put("pp_oauth2_scope", input.getProperty(OAuth2Helper.SCOPE));
		// Save the client ID too for later use
		newUser.stateVariables.put("pp_oauth2_clientid", input.getProperty(OAuth2Helper.CLIENT_ID));
		
		byte[] persistedSession;
		try {
			persistedSession = persistSession(newUser);
		} catch (IOException e) {
			throw new PTException(e);
		}

		if (input.getProperty(OAuth2Helper.RESPONSE_TYPE).equalsIgnoreCase(OAuth2Helper.CODE)) {
			// Tell dispatcher to disable IP checking for this particular session(bearer token), since it is meant
			// to be used by another server instead of the same client
			newUser.stateVariables.put("pp_disable_ipchecking", "true");			
		}

		String bearertoken = Base64.encode(UUID.randomUUID().toString());
		cache.put(bearertoken, persistedSession);
						
		
		if (input.getProperty(OAuth2Helper.RESPONSE_TYPE).equalsIgnoreCase(OAuth2Helper.TOKEN)) {
			// Implicit grant
			p.setProperty(OAuth2Helper.ACCESS_TOKEN, bearertoken);
			p.setProperty(OAuth2Helper.TOKEN_TYPE, "Bearer");
			p.setProperty(OAuth2Helper.EXPIRES_IN, "3600");
			return p;			
		} else {
			// Normal flow
			String authorizationToken = UUID.randomUUID().toString();
			
			AuthCacheEntry entry = new AuthCacheEntry();
			entry.bearertoken = bearertoken;
			entry.clientid = clientid;
			entry.redirectUri = input.getProperty(OAuth2Helper.REDIRECT_URI);
			authCache.put(authorizationToken, entry);
			
			p.setProperty(OAuth2Helper.AUTHORIZATION_CODE, authorizationToken);
			return p;			
		}
	}
	
	
	@Override
	public void createFromTicket(User user) throws PTException {
		// Ticket is in user.ticket
		
		byte[] ba = (byte[]) cache.get(user.ticket);
		
		if (ba == null)
			throw new PTException("Invalid OAuth ticket", AuthErrorCodes.ERROR_INVALIDCREDENTIALS, "Invalid or expired OAuth ticket");
		
		try {
			User restoredUser = restorePersistedSession(ba);
			
			// Save the ticket and session ID so we do not overwrite them
			restoredUser.ticket = user.ticket;
			restoredUser.sessionID = user.sessionID;
			
			// Restore everything from the persisted session to this one
			restoredUser.copyTo(user);
			
			user.addHistory("Session restored from persisted session using OAuth ticket: " + user.ticket);
		} catch (IOException e) {
			throw new PTException("Unable to restore persisted session", AuthErrorCodes.ERROR_GENERALERROR, "Unable to restore session", e);
		}
	}
}


...