| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jclouds.oauth.v2.functions; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| import static org.jclouds.oauth.v2.OAuthConstants.ADDITIONAL_CLAIMS; |
| import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE; |
| import static org.jclouds.oauth.v2.config.OAuthProperties.SCOPES; |
| import static org.jclouds.oauth.v2.config.OAuthProperties.SIGNATURE_OR_MAC_ALGORITHM; |
| |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| import javax.inject.Singleton; |
| |
| import org.jclouds.Constants; |
| import org.jclouds.oauth.v2.config.OAuthScopes; |
| import org.jclouds.oauth.v2.domain.ClaimSet; |
| import org.jclouds.oauth.v2.domain.Header; |
| import org.jclouds.oauth.v2.domain.OAuthCredentials; |
| import org.jclouds.oauth.v2.domain.TokenRequest; |
| import org.jclouds.oauth.v2.domain.TokenRequestFormat; |
| import org.jclouds.rest.internal.GeneratedHttpRequest; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Supplier; |
| import com.google.common.reflect.Invokable; |
| import com.google.inject.Inject; |
| import com.google.inject.name.Named; |
| |
| /** |
| * The default authenticator. |
| * <p/> |
| * Builds the default token request with the following claims: iss,scope,aud,iat,exp. |
| * <p/> |
| * TODO scopes etc should come from the REST method and not from a global property |
| */ |
| @Singleton |
| public class BuildTokenRequest implements Function<GeneratedHttpRequest, TokenRequest> { |
| |
| private final String assertionTargetDescription; |
| private final String signatureAlgorithm; |
| private final TokenRequestFormat tokenRequestFormat; |
| private final Supplier<OAuthCredentials> credentialsSupplier; |
| private final long tokenDuration; |
| |
| @Inject(optional = true) |
| @Named(ADDITIONAL_CLAIMS) |
| protected Map<String, String> additionalClaims = Collections.emptyMap(); |
| |
| @Inject(optional = true) |
| @Named(SCOPES) |
| protected String globalScopes = null; |
| |
| // injectable so expect tests can override with a predictable value |
| @Inject(optional = true) |
| protected Supplier<Long> timeSourceMillisSinceEpoch = new Supplier<Long>() { |
| @Override |
| public Long get() { |
| return System.currentTimeMillis(); |
| } |
| }; |
| |
| @Inject |
| public BuildTokenRequest(@Named(AUDIENCE) String assertionTargetDescription, |
| @Named(SIGNATURE_OR_MAC_ALGORITHM) String signatureAlgorithm, |
| TokenRequestFormat tokenRequestFormat, Supplier<OAuthCredentials> credentialsSupplier, |
| @Named(Constants.PROPERTY_SESSION_INTERVAL) long tokenDuration) { |
| this.assertionTargetDescription = assertionTargetDescription; |
| this.signatureAlgorithm = signatureAlgorithm; |
| this.tokenRequestFormat = tokenRequestFormat; |
| this.credentialsSupplier = credentialsSupplier; |
| this.tokenDuration = tokenDuration; |
| } |
| |
| @Override |
| public TokenRequest apply(GeneratedHttpRequest request) { |
| long now = timeSourceMillisSinceEpoch.get() / 1000; |
| |
| // fetch the token |
| Header header = Header.create(signatureAlgorithm, tokenRequestFormat.type()); |
| |
| Map<String, String> claims = new LinkedHashMap<String, String>(); |
| claims.put("iss", credentialsSupplier.get().identity); |
| claims.put("scope", getOAuthScopes(request)); |
| claims.put("aud", assertionTargetDescription); |
| claims.putAll(additionalClaims); |
| |
| checkState(claims.keySet().containsAll(tokenRequestFormat.requiredClaims()), |
| "not all required claims were present"); |
| |
| ClaimSet claimSet = ClaimSet.create(now, now + tokenDuration, Collections.unmodifiableMap(claims)); |
| |
| return TokenRequest.create(header, claimSet); |
| } |
| |
| protected String getOAuthScopes(GeneratedHttpRequest request) { |
| Invokable<?, ?> invokable = request.getInvocation().getInvokable(); |
| |
| OAuthScopes classScopes = invokable.getOwnerType().getRawType().getAnnotation(OAuthScopes.class); |
| OAuthScopes methodScopes = invokable.getAnnotation(OAuthScopes.class); |
| |
| // if no annotations are present the rely on globally set scopes |
| if (classScopes == null && methodScopes == null) { |
| checkState(globalScopes != null, String.format("REST class or method should be annotated " + |
| "with OAuthScopes specifying required permissions. Alternatively a global property " + |
| "\"oauth.scopes\" may be set to define scopes globally. REST Class: %s, Method: %s", |
| invokable.getOwnerType(), |
| invokable.getName())); |
| return globalScopes; |
| } |
| |
| OAuthScopes scopes = methodScopes != null ? methodScopes : classScopes; |
| return Joiner.on(",").join(scopes.value()); |
| } |
| } |