package com.janrain.oauth2; import com.janrain.backplane.common.BackplaneServerException; import com.janrain.backplane.dao.DaoException; import com.janrain.backplane.server2.dao.BP2DAOs; import com.janrain.backplane.server2.model.*; import com.janrain.backplane.server2.oauth2.model.Token; import com.janrain.backplane2.server.GrantType; import com.janrain.backplane2.server.Scope; import com.janrain.backplane2.server.TokenBuilder; import com.janrain.commons.supersimpledb.SimpleDBException; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import scala.Option; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; /** * @author Johnny Bufu */ public class AnonymousTokenRequest implements TokenRequest { // - PUBLIC public AnonymousTokenRequest( String callback, String bus, String scope, HttpServletRequest request) throws TokenException, DaoException { Option<Token> token = Token.fromRequest(request); this.grantType = token.isDefined() ? GrantType.REFRESH_ANONYMOUS : GrantType.ANONYMOUS; if (StringUtils.isBlank(callback)) { throw new TokenException("Callback cannot be blank"); } if (!callback.matches("[\\._a-zA-Z0-9]*")) { throw new TokenException("callback parameter value is malformed"); } this.requestScope = new Scope(scope); if ( this.requestScope.isAuthorizationRequired() || ( this.requestScope.getScopeFieldValues(Backplane2MessageFields.CHANNEL()) != null && ! this.requestScope.getScopeFieldValues(Backplane2MessageFields.CHANNEL()).isEmpty())) { throw new TokenException(OAuth2.OAUTH2_TOKEN_INVALID_SCOPE, "Buses and channels not allowed in the scope of anonymous token requests"); } if (token.isDefined()) { this.refreshToken = token.get(); if ( ! this.refreshToken.grantType().isRefresh()) { logger.warn("access token presented where refresh token is expected: " + refreshToken); throw new TokenException(OAuth2.OAUTH2_TOKEN_INVALID_REQUEST, "invalid token: " + refreshToken); } if (! this.refreshToken.scope().containsScope(this.requestScope)) { throw new TokenException(OAuth2.OAUTH2_TOKEN_INVALID_SCOPE, "invalid scope for refresh token: " + refreshToken + " : " + scope); } } if ( (!token.isDefined()) ^ StringUtils.isNotEmpty(bus)) { throw new TokenException("bus parameter is required if and only if refresh_token is not present"); } try { if (StringUtils.isNotEmpty(bus)) { this.busConfig = BP2DAOs.busDao().get(bus).getOrElse(null); if ( this.busConfig == null) { throw new TokenException("Invalid bus: " + bus); } } } catch (Exception e) { logger.error("error processing anonymous token request: " + e.getMessage(), e); throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing anonymous token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } // todo: check this properly, perhaps in controller? // throw new TokenException("Must not include client_secret for anonymous token requests"); } @Override public Map<String,Object> tokenResponse() throws TokenException { logger.info("Responding to anonymous token request..."); final Token accessToken; final Integer expiresIn = grantType.getAccessType().getTokenExpiresSecondsDefault(); Date expires = new Date(System.currentTimeMillis() + expiresIn.longValue() * 1000); try { Channel channel = createOrRefreshChannel(10 * expiresIn); Scope processedScope = processScope(channel.id(), (String) channel.get(ChannelFields.BUS()).getOrElse(null)); accessToken = new TokenBuilder(grantType.getAccessType(), processedScope.toString()).expires(expires).buildToken(); BP2DAOs.tokenDao().store(accessToken); return accessToken.response(generateRefreshToken(grantType.getRefreshType(), processedScope)); } catch (Exception e) { logger.error("error processing anonymous access token request: " + e.getMessage(), e); throw new TokenException(OAuth2.OAUTH2_TOKEN_SERVER_ERROR, "error processing anonymous token request", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { logger.info("exiting anonymous token request"); try { if (this.refreshToken != null) { BP2DAOs.tokenDao().delete(this.refreshToken.id()); } } catch (DaoException e) { logger.error("error deleting used refresh token: " + refreshToken.id(), e); } } } // - PRIVATE private static final Logger logger = Logger.getLogger(AnonymousTokenRequest.class); private final GrantType grantType; private final Scope requestScope; private Token refreshToken; private BusConfig2 busConfig; private static String generateRefreshToken(GrantType refreshType, Scope scope) throws BackplaneServerException, DaoException { if (refreshType == null || ! refreshType.isRefresh()) return null; Token refreshToken = new TokenBuilder(refreshType, scope.toString()).buildToken(); BP2DAOs.tokenDao().store(refreshToken); return refreshToken.id(); } private Channel createOrRefreshChannel(int expireSeconds) throws TokenException, SimpleDBException, BackplaneServerException, DaoException { String channelId = null; BusConfig2 config; if (refreshToken != null ) { final Set<String> channels = refreshToken.scope().getScopeFieldValues(Backplane2MessageFields.CHANNEL()); final Set<String> buses = refreshToken.scope().getScopeFieldValues(Backplane2MessageFields.BUS()); if ( channels == null || channels.isEmpty() || channels.size() > 1 || buses == null || buses.isEmpty() || buses.size() > 1 ) { throw new TokenException("invalid anonymous refresh token: " + refreshToken.id()); } else { config = BP2DAOs.busDao().get(buses.iterator().next()).getOrElse(null); channelId = channels.iterator().next(); } } else { config = busConfig; } Channel channel = new Channel(channelId, config, expireSeconds); BP2DAOs.channelDao().store(channel); return channel; } private Scope processScope(final String channel, final String bus) { Map<Backplane2MessageFields.EnumVal,LinkedHashSet<String>> scopeMap = new LinkedHashMap<Backplane2MessageFields.EnumVal, LinkedHashSet<String>>(); scopeMap.putAll(requestScope.getScopeMap()); scopeMap.put(Backplane2MessageFields.BUS(), new LinkedHashSet<String>() {{ add(bus);}}); scopeMap.put(Backplane2MessageFields.CHANNEL(), new LinkedHashSet<String>() {{ add(channel);}}); return new Scope(scopeMap); } }