mirror of https://github.com/apache/jclouds.git
Promote the OAuth v2 API
This commit is contained in:
commit
1c8b7764f6
|
@ -0,0 +1,16 @@
|
||||||
|
In order to use oauth applications must specify the following properties:
|
||||||
|
|
||||||
|
Mandatory:
|
||||||
|
<myprovider>.identity - the oauth identity (e.g., service account email in Google Api's)
|
||||||
|
<myprovider>.credential - the private key used to sign requests, in pem format
|
||||||
|
oauth.endpoint - the endpoint to use for authentication (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
|
||||||
|
oauth.audience - the "audience" of the token request (e.g., "http://accounts.google.com/o/oauth2/token" in Google Api's)
|
||||||
|
|
||||||
|
Running the live test:
|
||||||
|
|
||||||
|
mvn clean install -Plive\
|
||||||
|
-Dtest.oauth.identity=<accout email>\
|
||||||
|
-Dtest.oauth.credential=<accout pk in pem format>\
|
||||||
|
-Dtest.oauth.endpoint=https://accounts.google.com/o/oauth2/token\
|
||||||
|
-Dtest.jclouds.oauth.audience=https://accounts.google.com/o/oauth2/token\
|
||||||
|
-Dtest.jclouds.oauth.scope=https://www.googleapis.com/auth/prediction
|
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.apache.jclouds.labs</groupId>
|
||||||
|
<artifactId>jclouds-labs-google</artifactId>
|
||||||
|
<version>2.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- TODO: when out of labs, switch to org.jclouds.api -->
|
||||||
|
<groupId>org.apache.jclouds.labs</groupId>
|
||||||
|
<artifactId>oauth</artifactId>
|
||||||
|
<name>jclouds OAuth core</name>
|
||||||
|
<description>jclouds components to access OAuth</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<test.oauth.identity>FIX_ME</test.oauth.identity>
|
||||||
|
<test.oauth.credential>FIX_ME</test.oauth.credential>
|
||||||
|
<test.oauth.endpoint>FIX_ME</test.oauth.endpoint>
|
||||||
|
<test.jclouds.oauth.audience>FIX_ME</test.jclouds.oauth.audience>
|
||||||
|
<test.jclouds.oauth.scope>FIX_ME</test.jclouds.oauth.scope>
|
||||||
|
<test.oauth.api-version>2</test.oauth.api-version>
|
||||||
|
<test.oauth.build-version />
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jclouds</groupId>
|
||||||
|
<artifactId>jclouds-core</artifactId>
|
||||||
|
<version>${jclouds.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.auto.value</groupId>
|
||||||
|
<artifactId>auto-value</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jclouds</groupId>
|
||||||
|
<artifactId>jclouds-core</artifactId>
|
||||||
|
<version>${jclouds.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.jclouds.driver</groupId>
|
||||||
|
<artifactId>jclouds-slf4j</artifactId>
|
||||||
|
<version>${jclouds.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.squareup.okhttp</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>live</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-test</id>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>integration</id>
|
||||||
|
<phase>integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<test.oauth.identity>${test.oauth.identity}</test.oauth.identity>
|
||||||
|
<test.oauth.credential>${test.oauth.credential}</test.oauth.credential>
|
||||||
|
<test.oauth.endpoint>${test.oauth.endpoint}</test.oauth.endpoint>
|
||||||
|
<test.oauth.api-version>${test.oauth.api-version}</test.oauth.api-version>
|
||||||
|
<test.oauth.build-version>${test.oauth.build-version}</test.oauth.build-version>
|
||||||
|
<test.jclouds.oauth.jws-alg>${test.jclouds.oauth.jws-alg}</test.jclouds.oauth.jws-alg>
|
||||||
|
<test.jclouds.oauth.audience>${test.jclouds.oauth.audience}</test.jclouds.oauth.audience>
|
||||||
|
<test.jclouds.oauth.scope>${test.jclouds.oauth.scope}</test.jclouds.oauth.scope>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
|
||||||
|
import org.jclouds.oauth.v2.functions.ClaimsToAssertion;
|
||||||
|
import org.jclouds.oauth.v2.config.Authorization;
|
||||||
|
import org.jclouds.oauth.v2.domain.Claims;
|
||||||
|
import org.jclouds.oauth.v2.domain.Token;
|
||||||
|
import org.jclouds.rest.annotations.Endpoint;
|
||||||
|
import org.jclouds.rest.annotations.FormParams;
|
||||||
|
import org.jclouds.rest.annotations.ParamParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds to an OAuth2 <a href="http://tools.ietf.org/html/rfc6749#section-3.1">authorization endpoint</a>.
|
||||||
|
*/
|
||||||
|
@Endpoint(Authorization.class)
|
||||||
|
public interface AuthorizationApi extends Closeable {
|
||||||
|
@Named("oauth2:authorize")
|
||||||
|
@POST
|
||||||
|
@FormParams(keys = "grant_type", values = "urn:ietf:params:oauth:grant-type:jwt-bearer")
|
||||||
|
@Consumes(APPLICATION_JSON)
|
||||||
|
Token authorize(@FormParam("assertion") @ParamParser(ClaimsToAssertion.class) Claims claims);
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import javax.inject.Qualifier;
|
||||||
|
|
||||||
|
@Retention(value = RetentionPolicy.RUNTIME)
|
||||||
|
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
|
||||||
|
@Qualifier
|
||||||
|
public @interface Authorization {
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
|
||||||
|
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
/** Defines the contents of the credential field in {@link org.jclouds.ContextBuilder#credentials(String, String)}. */
|
||||||
|
public enum CredentialType {
|
||||||
|
|
||||||
|
BEARER_TOKEN_CREDENTIALS,
|
||||||
|
|
||||||
|
/** Contents are a PEM-encoded P12 Private Key. */
|
||||||
|
P12_PRIVATE_KEY_CREDENTIALS;
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
|
return UPPER_UNDERSCORE.to(LOWER_CAMEL, name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CredentialType fromValue(String credentialType) {
|
||||||
|
return valueOf(LOWER_CAMEL.to(UPPER_UNDERSCORE, checkNotNull(credentialType, "credentialType")));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import static org.jclouds.oauth.v2.config.CredentialType.P12_PRIVATE_KEY_CREDENTIALS;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
|
||||||
|
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.jclouds.oauth.v2.AuthorizationApi;
|
||||||
|
import org.jclouds.oauth.v2.filters.BearerTokenFromCredentials;
|
||||||
|
import org.jclouds.oauth.v2.filters.JWTBearerTokenFlow;
|
||||||
|
import org.jclouds.oauth.v2.filters.OAuthFilter;
|
||||||
|
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Provides;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
|
public final class OAuthModule extends AbstractModule {
|
||||||
|
|
||||||
|
@Override protected void configure() {
|
||||||
|
bindHttpApi(binder(), AuthorizationApi.class);
|
||||||
|
bind(CredentialType.class).toProvider(CredentialTypeFromPropertyOrDefault.class);
|
||||||
|
bind(new TypeLiteral<Supplier<PrivateKey>>() {}).annotatedWith(Authorization.class).to(PrivateKeySupplier.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Authorization
|
||||||
|
protected Supplier<URI> oauthEndpoint(@javax.inject.Named("oauth.endpoint") String endpoint) {
|
||||||
|
return Suppliers.ofInstance(URI.create(endpoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public static class CredentialTypeFromPropertyOrDefault implements Provider<CredentialType> {
|
||||||
|
@Inject(optional = true)
|
||||||
|
@Named(CREDENTIAL_TYPE)
|
||||||
|
String credentialType = P12_PRIVATE_KEY_CREDENTIALS.toString();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CredentialType get() {
|
||||||
|
return CredentialType.fromValue(credentialType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
protected OAuthFilter authenticationFilterForCredentialType(CredentialType credentialType,
|
||||||
|
JWTBearerTokenFlow serviceAccountAuth,
|
||||||
|
BearerTokenFromCredentials bearerTokenAuth) {
|
||||||
|
switch (credentialType) {
|
||||||
|
case P12_PRIVATE_KEY_CREDENTIALS:
|
||||||
|
return serviceAccountAuth;
|
||||||
|
case BEARER_TOKEN_CREDENTIALS:
|
||||||
|
return bearerTokenAuth;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported credential type: " + credentialType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
public final class OAuthProperties {
|
||||||
|
|
||||||
|
/** The JSON Web Signature alg, must be {@code RS256} or {@code none}. */
|
||||||
|
public static final String JWS_ALG = "jclouds.oauth.jws-alg";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oauth audience, who this token is intended for. For instance in JWT and for
|
||||||
|
* google API's this property maps to: {"aud","https://accounts.google.com/o/oauth2/token"}
|
||||||
|
*
|
||||||
|
* @see <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30">doc</a>
|
||||||
|
*/
|
||||||
|
public static final String AUDIENCE = "jclouds.oauth.audience";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify if credentials are id + private key or if you are reusing an oauth2 token.
|
||||||
|
*
|
||||||
|
* @see org.jclouds.oauth.v2.config.CredentialType
|
||||||
|
*/
|
||||||
|
public static final String CREDENTIAL_TYPE = "jclouds.oauth.credential-type";
|
||||||
|
|
||||||
|
private OAuthProperties() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jclouds.http.HttpRequest;
|
||||||
|
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations are api-specific, typically routing GET or HEAD requests to a read-only role, and others to a
|
||||||
|
* read-write one.
|
||||||
|
*/
|
||||||
|
public interface OAuthScopes {
|
||||||
|
|
||||||
|
/** Returns a list of scopes needed to perform the request. */
|
||||||
|
List<String> forRequest(HttpRequest input);
|
||||||
|
|
||||||
|
@AutoValue public abstract static class SingleScope implements OAuthScopes {
|
||||||
|
abstract List<String> scopes();
|
||||||
|
|
||||||
|
public static SingleScope create(String scope) {
|
||||||
|
return new AutoValue_OAuthScopes_SingleScope(ImmutableList.of(scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public List<String> forRequest(HttpRequest input) {
|
||||||
|
return scopes();
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleScope() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoValue public abstract static class ReadOrWriteScopes implements OAuthScopes {
|
||||||
|
abstract List<String> readScopes();
|
||||||
|
|
||||||
|
abstract List<String> writeScopes();
|
||||||
|
|
||||||
|
public static ReadOrWriteScopes create(String readScope, String writeScope) {
|
||||||
|
return new AutoValue_OAuthScopes_ReadOrWriteScopes( //
|
||||||
|
ImmutableList.of(readScope), //
|
||||||
|
ImmutableList.of(writeScope) //
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public List<String> forRequest(HttpRequest input) {
|
||||||
|
if (input.getMethod().equals("GET") || input.getMethod().equals("HEAD")) {
|
||||||
|
return readScopes();
|
||||||
|
}
|
||||||
|
return writeScopes();
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOrWriteScopes() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import static com.google.common.base.Charsets.UTF_8;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Throwables.propagate;
|
||||||
|
import static org.jclouds.crypto.Pems.privateKeySpec;
|
||||||
|
import static org.jclouds.util.Throwables2.getFirstThrowableOfType;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import org.jclouds.domain.Credentials;
|
||||||
|
import org.jclouds.location.Provider;
|
||||||
|
import org.jclouds.rest.AuthorizationException;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads {@link PrivateKey} from a pem private key using and RSA KeyFactory. The pem pk algorithm must match the
|
||||||
|
* KeyFactory algorithm.
|
||||||
|
*/
|
||||||
|
@Singleton // due to cache
|
||||||
|
final class PrivateKeySupplier implements Supplier<PrivateKey> {
|
||||||
|
|
||||||
|
private final Supplier<Credentials> creds;
|
||||||
|
private final LoadingCache<Credentials, PrivateKey> keyCache;
|
||||||
|
|
||||||
|
@Inject PrivateKeySupplier(@Provider Supplier<Credentials> creds, PrivateKeyForCredentials loader) {
|
||||||
|
this.creds = creds;
|
||||||
|
// throw out the private key related to old credentials
|
||||||
|
this.keyCache = CacheBuilder.newBuilder().maximumSize(2).build(checkNotNull(loader, "loader"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* it is relatively expensive to extract a private key from a PEM. cache the relationship between current
|
||||||
|
* credentials
|
||||||
|
* so that the private key is only recalculated once.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static final class PrivateKeyForCredentials extends CacheLoader<Credentials, PrivateKey> {
|
||||||
|
|
||||||
|
@Override public PrivateKey load(Credentials in) {
|
||||||
|
try {
|
||||||
|
String privateKeyInPemFormat = checkNotNull(in.credential, "credential in PEM format");
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||||
|
return keyFactory.generatePrivate(privateKeySpec(ByteSource.wrap(privateKeyInPemFormat.getBytes(UTF_8))));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw propagate(e);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new AuthorizationException("security exception loading credentials. " + e.getMessage(), e);
|
||||||
|
// catch IAE that is thrown when parsing the pk fails
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new AuthorizationException("cannot parse pk. " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public PrivateKey get() {
|
||||||
|
try {
|
||||||
|
// loader always throws UncheckedExecutionException so no point in using get()
|
||||||
|
return keyCache.getUnchecked(checkNotNull(creds.get(), "credential supplier returned null"));
|
||||||
|
} catch (UncheckedExecutionException e) {
|
||||||
|
AuthorizationException authorizationException = getFirstThrowableOfType(e, AuthorizationException.class);
|
||||||
|
if (authorizationException != null) {
|
||||||
|
throw authorizationException;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.domain;
|
||||||
|
|
||||||
|
import org.jclouds.json.SerializedNames;
|
||||||
|
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claims corresponding to a {@linkplain Token JWT Token}.
|
||||||
|
*
|
||||||
|
* @see <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4">registered list</a>
|
||||||
|
*/
|
||||||
|
@AutoValue
|
||||||
|
public abstract class Claims {
|
||||||
|
/** The issuer of this token. In google, the service account email. */
|
||||||
|
public abstract String iss();
|
||||||
|
|
||||||
|
/** A comma-separated list of scopes needed to perform the request. */
|
||||||
|
public abstract String scope();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oauth audience, who this token is intended for. For instance in JWT and for
|
||||||
|
* google API's, this maps to: {@code https://accounts.google.com/o/oauth2/token}
|
||||||
|
*/
|
||||||
|
public abstract String aud();
|
||||||
|
|
||||||
|
/** The expiration time, in seconds since {@link #iat()}. */
|
||||||
|
public abstract long exp();
|
||||||
|
|
||||||
|
/** The time at which the JWT was issued, in seconds since the epoch. */
|
||||||
|
public abstract long iat();
|
||||||
|
|
||||||
|
@SerializedNames({ "iss", "scope", "aud", "exp", "iat" })
|
||||||
|
public static Claims create(String iss, String scope, String aud, long exp, long iat) {
|
||||||
|
return new AutoValue_Claims(iss, scope, aud, exp, iat);
|
||||||
|
}
|
||||||
|
|
||||||
|
Claims() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.domain;
|
||||||
|
|
||||||
|
import org.jclouds.json.SerializedNames;
|
||||||
|
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The oauth token, obtained upon a successful token request and ready to embed in requests.
|
||||||
|
*/
|
||||||
|
@AutoValue
|
||||||
|
public abstract class Token {
|
||||||
|
/** The access token obtained from the OAuth server. */
|
||||||
|
public abstract String accessToken();
|
||||||
|
|
||||||
|
/** The type of the token, e.g., {@code Bearer}. */
|
||||||
|
public abstract String tokenType();
|
||||||
|
|
||||||
|
/** In how many seconds this token expires. */
|
||||||
|
public abstract long expiresIn();
|
||||||
|
|
||||||
|
@SerializedNames({"access_token", "token_type", "expires_in"})
|
||||||
|
public static Token create(String accessToken, String tokenType, long expiresIn) {
|
||||||
|
return new AutoValue_Token(accessToken, tokenType, expiresIn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.filters;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.jclouds.domain.Credentials;
|
||||||
|
import org.jclouds.http.HttpException;
|
||||||
|
import org.jclouds.http.HttpRequest;
|
||||||
|
import org.jclouds.location.Provider;
|
||||||
|
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the user supplies {@link org.jclouds.oauth.v2.config.CredentialType#BEARER_TOKEN_CREDENTIALS}, the credential
|
||||||
|
* is a literal bearer token. This filter applies that to the request.
|
||||||
|
*/
|
||||||
|
public final class BearerTokenFromCredentials implements OAuthFilter {
|
||||||
|
private final Supplier<Credentials> creds;
|
||||||
|
|
||||||
|
@Inject BearerTokenFromCredentials(@Provider Supplier<Credentials> creds) {
|
||||||
|
this.creds = creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
|
return request.toBuilder().addHeader("Authorization", format("%s %s", "Bearer", creds.get().credential)).build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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.filters;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.domain.Credentials;
|
||||||
|
import org.jclouds.http.HttpException;
|
||||||
|
import org.jclouds.http.HttpRequest;
|
||||||
|
import org.jclouds.location.Provider;
|
||||||
|
import org.jclouds.oauth.v2.AuthorizationApi;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||||
|
import org.jclouds.oauth.v2.domain.Claims;
|
||||||
|
import org.jclouds.oauth.v2.domain.Token;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorizes new Bearer Tokens at runtime by authorizing claims needed for the http request.
|
||||||
|
*
|
||||||
|
* <h3>Cache</h3>
|
||||||
|
* This maintains a time-based Bearer Token cache. By default expires after 59 minutes
|
||||||
|
* (the maximum time a token is valid is 60 minutes).
|
||||||
|
* This cache and expiry period is system-wide and does not attend to per-instance expiry time
|
||||||
|
* (e.g. "expires_in" from Google Compute -- which is set to the standard 3600 seconds).
|
||||||
|
*/
|
||||||
|
public class JWTBearerTokenFlow implements OAuthFilter {
|
||||||
|
private static final Joiner ON_COMMA = Joiner.on(",");
|
||||||
|
|
||||||
|
private final String audience;
|
||||||
|
private final Supplier<Credentials> credentialsSupplier;
|
||||||
|
private final OAuthScopes scopes;
|
||||||
|
private final long tokenDuration;
|
||||||
|
private final LoadingCache<Claims, Token> tokenCache;
|
||||||
|
|
||||||
|
public static class TestJWTBearerTokenFlow extends JWTBearerTokenFlow {
|
||||||
|
|
||||||
|
@Inject TestJWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
|
||||||
|
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||||
|
super(loader, tokenDuration, audience, credentialsSupplier, scopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constant time for testing. */
|
||||||
|
long currentTimeSeconds() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject JWTBearerTokenFlow(AuthorizeToken loader, @Named(PROPERTY_SESSION_INTERVAL) long tokenDuration,
|
||||||
|
@Named(AUDIENCE) String audience, @Provider Supplier<Credentials> credentialsSupplier, OAuthScopes scopes) {
|
||||||
|
this.audience = audience;
|
||||||
|
this.credentialsSupplier = credentialsSupplier;
|
||||||
|
this.scopes = scopes;
|
||||||
|
this.tokenDuration = tokenDuration;
|
||||||
|
// since the session interval is also the token expiration time requested to the server make the token expire a
|
||||||
|
// bit before the deadline to make sure there aren't session expiration exceptions
|
||||||
|
long cacheExpirationSeconds = tokenDuration > 30 ? tokenDuration - 30 : tokenDuration;
|
||||||
|
this.tokenCache = CacheBuilder.newBuilder().expireAfterWrite(tokenDuration, SECONDS).build(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class AuthorizeToken extends CacheLoader<Claims, Token> {
|
||||||
|
private final AuthorizationApi api;
|
||||||
|
|
||||||
|
@Inject AuthorizeToken(AuthorizationApi api) {
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Token load(Claims key) throws Exception {
|
||||||
|
return api.authorize(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public HttpRequest filter(HttpRequest request) throws HttpException {
|
||||||
|
long now = currentTimeSeconds();
|
||||||
|
Claims claims = Claims.create( //
|
||||||
|
credentialsSupplier.get().identity, // iss
|
||||||
|
ON_COMMA.join(scopes.forRequest(request)), // scope
|
||||||
|
audience, // aud
|
||||||
|
now + tokenDuration, // exp
|
||||||
|
now // iat
|
||||||
|
);
|
||||||
|
Token token = tokenCache.getUnchecked(claims);
|
||||||
|
String authorization = String.format("%s %s", token.tokenType(), token.accessToken());
|
||||||
|
return request.toBuilder().addHeader("Authorization", authorization).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
long currentTimeSeconds() {
|
||||||
|
return System.currentTimeMillis() / 1000;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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.filters;
|
||||||
|
|
||||||
|
import org.jclouds.http.HttpRequestFilter;
|
||||||
|
|
||||||
|
/** Indicates use of auth mechanism according to {@link org.jclouds.oauth.v2.config.OAuthProperties#CREDENTIAL_TYPE). */
|
||||||
|
public interface OAuthFilter extends HttpRequestFilter {
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* 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.Charsets.UTF_8;
|
||||||
|
import static com.google.common.base.Joiner.on;
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.io.BaseEncoding.base64Url;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.jclouds.json.Json;
|
||||||
|
import org.jclouds.oauth.v2.config.Authorization;
|
||||||
|
import org.jclouds.rest.AuthorizationException;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
public final class ClaimsToAssertion implements Function<Object, String> {
|
||||||
|
private static final List<String> SUPPORTED_ALGS = ImmutableList.of("RS256", "none");
|
||||||
|
|
||||||
|
private final Supplier<PrivateKey> privateKey;
|
||||||
|
private final Json json;
|
||||||
|
private final String alg;
|
||||||
|
|
||||||
|
@Inject ClaimsToAssertion(@Named(JWS_ALG) String alg, @Authorization Supplier<PrivateKey> privateKey, Json json) {
|
||||||
|
this.alg = alg;
|
||||||
|
checkArgument(SUPPORTED_ALGS.contains(alg), "%s %s not in supported list", JWS_ALG, alg, SUPPORTED_ALGS);
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
this.json = json;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String apply(Object input) {
|
||||||
|
String encodedHeader = String.format("{\"alg\":\"%s\",\"typ\":\"JWT\"}", alg);
|
||||||
|
String encodedClaimSet = json.toJson(input);
|
||||||
|
|
||||||
|
encodedHeader = base64Url().omitPadding().encode(encodedHeader.getBytes(UTF_8));
|
||||||
|
encodedClaimSet = base64Url().omitPadding().encode(encodedClaimSet.getBytes(UTF_8));
|
||||||
|
|
||||||
|
byte[] signature = alg.equals("none")
|
||||||
|
? null
|
||||||
|
: sha256(privateKey.get(), on(".").join(encodedHeader, encodedClaimSet).getBytes(UTF_8));
|
||||||
|
String encodedSignature = signature != null ? base64Url().omitPadding().encode(signature) : "";
|
||||||
|
|
||||||
|
// the final assertion in base 64 encoded {header}.{claimSet}.{signature} format
|
||||||
|
return on(".").join(encodedHeader, encodedClaimSet, encodedSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] sha256(PrivateKey privateKey, byte[] input) {
|
||||||
|
try {
|
||||||
|
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||||
|
signature.initSign(privateKey);
|
||||||
|
signature.update(input);
|
||||||
|
return signature.sign();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
throw new AuthorizationException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new AuthorizationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static org.jclouds.oauth.v2.OAuthTestUtils.setCredential;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||||
|
import static org.jclouds.providers.AnonymousProviderMetadata.forApiOnEndpoint;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.jclouds.apis.BaseApiLiveTest;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthModule;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||||
|
import org.jclouds.oauth.v2.domain.Claims;
|
||||||
|
import org.jclouds.oauth.v2.domain.Token;
|
||||||
|
import org.jclouds.providers.ProviderMetadata;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
|
|
||||||
|
@Test(groups = "live", singleThreaded = true)
|
||||||
|
public class AuthorizationApiLiveTest extends BaseApiLiveTest<AuthorizationApi> {
|
||||||
|
|
||||||
|
private final String jwsAlg = "RS256";
|
||||||
|
private String scope;
|
||||||
|
private String audience;
|
||||||
|
|
||||||
|
public AuthorizationApiLiveTest() {
|
||||||
|
provider = "oauth";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void authenticateJWTToken() throws Exception {
|
||||||
|
long now = System.currentTimeMillis() / 1000;
|
||||||
|
Claims claims = Claims.create(
|
||||||
|
identity, // iss
|
||||||
|
scope, // scope
|
||||||
|
audience, // aud
|
||||||
|
now + 3600, // exp
|
||||||
|
now // iat
|
||||||
|
);
|
||||||
|
|
||||||
|
Token token = api.authorize(claims);
|
||||||
|
|
||||||
|
assertNotNull(token, "no token when authorizing " + claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** OAuth isn't registered as a provider intentionally, so we fake one. */
|
||||||
|
@Override protected ProviderMetadata createProviderMetadata() {
|
||||||
|
return forApiOnEndpoint(AuthorizationApi.class, endpoint).toBuilder().id("oauth").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Properties setupProperties() {
|
||||||
|
Properties props = super.setupProperties();
|
||||||
|
props.setProperty(JWS_ALG, jwsAlg);
|
||||||
|
credential = setCredential(props, "oauth.credential");
|
||||||
|
audience = checkNotNull(setIfTestSystemPropertyPresent(props, AUDIENCE), "test.jclouds.oauth.audience");
|
||||||
|
scope = checkNotNull(setIfTestSystemPropertyPresent(props, "jclouds.oauth.scope"), "test.jclouds.oauth.scope");
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Iterable<Module> setupModules() {
|
||||||
|
return ImmutableList.<Module>builder() //
|
||||||
|
.add(new OAuthModule()) //
|
||||||
|
.add(new Module() {
|
||||||
|
@Override public void configure(Binder binder) {
|
||||||
|
// ContextBuilder erases oauth.endpoint, as that's the same name as the provider key.
|
||||||
|
binder.bindConstant().annotatedWith(Names.named("oauth.endpoint")).to(endpoint);
|
||||||
|
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(scope));
|
||||||
|
}
|
||||||
|
}).addAll(super.setupModules()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.google.common.base.Charsets.UTF_8;
|
||||||
|
import static com.google.common.io.BaseEncoding.base64Url;
|
||||||
|
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
|
||||||
|
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
|
||||||
|
import static org.jclouds.oauth.v2.config.CredentialType.P12_PRIVATE_KEY_CREDENTIALS;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.JWS_ALG;
|
||||||
|
import static org.jclouds.util.Strings2.toStringAndClose;
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.jclouds.ContextBuilder;
|
||||||
|
import org.jclouds.concurrent.config.ExecutorServiceModule;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthModule;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthScopes;
|
||||||
|
import org.jclouds.oauth.v2.config.OAuthScopes.SingleScope;
|
||||||
|
import org.jclouds.oauth.v2.domain.Claims;
|
||||||
|
import org.jclouds.oauth.v2.domain.Token;
|
||||||
|
import org.jclouds.rest.AnonymousHttpApiMetadata;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import com.google.inject.Binder;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockResponse;
|
||||||
|
import com.squareup.okhttp.mockwebserver.MockWebServer;
|
||||||
|
import com.squareup.okhttp.mockwebserver.RecordedRequest;
|
||||||
|
|
||||||
|
@Test(groups = "unit", testName = "OAuthApiMockTest")
|
||||||
|
public class AuthorizationApiMockTest {
|
||||||
|
private static final String SCOPE = "https://www.googleapis.com/auth/prediction";
|
||||||
|
|
||||||
|
private static final String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
|
||||||
|
|
||||||
|
private static final String claims = "{\"iss\":\"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer" +
|
||||||
|
".gserviceaccount.com\",\"scope\":\"" + SCOPE + "\",\"aud\":\"https://accounts.google" +
|
||||||
|
".com/o/oauth2/token\",\"exp\":1328573381,\"iat\":1328569781}";
|
||||||
|
|
||||||
|
private static final Token TOKEN = Token.create("1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M", "Bearer", 3600);
|
||||||
|
|
||||||
|
private static final Claims CLAIMS = Claims.create(
|
||||||
|
"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com", // iss
|
||||||
|
SCOPE, // scope
|
||||||
|
"https://accounts.google.com/o/oauth2/token", // aud
|
||||||
|
1328573381, // exp
|
||||||
|
1328569781 // iat
|
||||||
|
);
|
||||||
|
|
||||||
|
public void testGenerateJWTRequest() throws Exception {
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
server.enqueue(new MockResponse().setBody("{\n" +
|
||||||
|
" \"access_token\" : \"1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M\",\n" +
|
||||||
|
" \"token_type\" : \"Bearer\",\n" +
|
||||||
|
" \"expires_in\" : 3600\n" +
|
||||||
|
"}"));
|
||||||
|
server.play();
|
||||||
|
|
||||||
|
AuthorizationApi api = api(server.getUrl("/"));
|
||||||
|
|
||||||
|
assertEquals(api.authorize(CLAIMS), TOKEN);
|
||||||
|
|
||||||
|
RecordedRequest request = server.takeRequest();
|
||||||
|
assertEquals(request.getMethod(), "POST");
|
||||||
|
assertEquals(request.getHeader("Accept"), APPLICATION_JSON);
|
||||||
|
assertEquals(request.getHeader("Content-Type"), "application/x-www-form-urlencoded");
|
||||||
|
|
||||||
|
assertEquals(new String(request.getBody(), UTF_8), //
|
||||||
|
"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&" +
|
||||||
|
// Base64 Encoded Header
|
||||||
|
"assertion=" +
|
||||||
|
Joiner.on('.').join(encoding.encode(header.getBytes(UTF_8)), encoding.encode(claims.getBytes(UTF_8)),
|
||||||
|
// Base64 encoded {header}.{claims} signature (using SHA256)
|
||||||
|
"W2Lesr_98AzVYiMbzxFqmwcOjpIWlwqkC6pNn1fXND9oSDNNnFhy-AAR6DKH-x9ZmxbY80" +
|
||||||
|
"R5fH-OCeWumXlVgceKN8Z2SmgQsu8ElTpypQA54j_5j8vUImJ5hsOUYPeyF1U2BUzZ3L5g" +
|
||||||
|
"03PXBA0YWwRU9E1ChH28dQBYuGiUmYw"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BaseEncoding encoding = base64Url().omitPadding();
|
||||||
|
|
||||||
|
private AuthorizationApi api(URL url) throws IOException {
|
||||||
|
Properties overrides = new Properties();
|
||||||
|
overrides.setProperty("oauth.endpoint", url.toString());
|
||||||
|
overrides.setProperty(JWS_ALG, "RS256");
|
||||||
|
overrides.setProperty(CREDENTIAL_TYPE, P12_PRIVATE_KEY_CREDENTIALS.toString());
|
||||||
|
overrides.setProperty(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
|
||||||
|
overrides.setProperty(PROPERTY_MAX_RETRIES, "1");
|
||||||
|
|
||||||
|
return ContextBuilder.newBuilder(AnonymousHttpApiMetadata.forApi(AuthorizationApi.class))
|
||||||
|
.credentials("foo", toStringAndClose(OAuthTestUtils.class.getResourceAsStream("/testpk.pem")))
|
||||||
|
.endpoint(url.toString())
|
||||||
|
.overrides(overrides)
|
||||||
|
.modules(ImmutableSet.of(new ExecutorServiceModule(sameThreadExecutor()), new OAuthModule(), new Module() {
|
||||||
|
@Override public void configure(Binder binder) {
|
||||||
|
binder.bind(OAuthScopes.class).toInstance(SingleScope.create(SCOPE));
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.buildApi(AuthorizationApi.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Throwables.propagate;
|
||||||
|
import static org.jclouds.oauth.v2.config.CredentialType.BEARER_TOKEN_CREDENTIALS;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.AUDIENCE;
|
||||||
|
import static org.jclouds.oauth.v2.config.OAuthProperties.CREDENTIAL_TYPE;
|
||||||
|
import static org.jclouds.util.Strings2.toStringAndClose;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
|
public class OAuthTestUtils {
|
||||||
|
|
||||||
|
public static Properties defaultProperties(Properties properties) {
|
||||||
|
try {
|
||||||
|
properties = properties == null ? new Properties() : properties;
|
||||||
|
properties.put("oauth.identity", "foo");
|
||||||
|
properties.put("oauth.credential", toStringAndClose(OAuthTestUtils.class.getResourceAsStream("/testpk.pem")));
|
||||||
|
properties.put("oauth.endpoint", "http://localhost:5000/o/oauth2/token");
|
||||||
|
properties.put(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
|
||||||
|
return properties;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw propagate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Properties bearerTokenAuthProperties(Properties properties) {
|
||||||
|
properties = properties == null ? new Properties() : properties;
|
||||||
|
properties.put("oauth.identity", "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com");
|
||||||
|
properties.put("oauth.credential", "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M");
|
||||||
|
properties.put("oauth.endpoint", "http://localhost:5000/o/oauth2/token");
|
||||||
|
properties.put(AUDIENCE, "https://accounts.google.com/o/oauth2/token");
|
||||||
|
properties.put(CREDENTIAL_TYPE, BEARER_TOKEN_CREDENTIALS.toString());
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to jclouds-core
|
||||||
|
public static String setCredential(Properties overrides, String key) {
|
||||||
|
String val = null;
|
||||||
|
String credentialFromFile = null;
|
||||||
|
String testKey = "test." + key;
|
||||||
|
|
||||||
|
if (System.getProperties().containsKey(testKey)) {
|
||||||
|
val = System.getProperty(testKey);
|
||||||
|
}
|
||||||
|
checkNotNull(val, String.format("the property %s must be set (pem private key file path or private key as a string)", testKey));
|
||||||
|
|
||||||
|
if (val.startsWith("-----BEGIN")) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
credentialFromFile = Files.toString(new File(val), Charsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw propagate(e);
|
||||||
|
}
|
||||||
|
overrides.setProperty(key, credentialFromFile);
|
||||||
|
return credentialFromFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMandatoryProperty(Properties properties, String key) {
|
||||||
|
checkNotNull(properties);
|
||||||
|
checkNotNull(key);
|
||||||
|
String value = properties.getProperty(key);
|
||||||
|
return checkNotNull(value, String.format("mandatory property %s or test.%s was not present", key, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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.config;
|
||||||
|
|
||||||
|
import static com.google.common.base.Suppliers.ofInstance;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.jclouds.domain.Credentials;
|
||||||
|
import org.jclouds.oauth.v2.OAuthTestUtils;
|
||||||
|
import org.jclouds.oauth.v2.config.PrivateKeySupplier.PrivateKeyForCredentials;
|
||||||
|
import org.jclouds.rest.AuthorizationException;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
|
@Test(groups = "unit")
|
||||||
|
public class PrivateKeySupplierTest {
|
||||||
|
|
||||||
|
/** Test loading the credentials by extracting a pk from a PKCS12 keystore. */
|
||||||
|
public void testLoadPKString() throws Exception {
|
||||||
|
assertNotNull(loadPrivateKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = AuthorizationException.class)
|
||||||
|
public void testAuthorizationExceptionIsThrownOnBadKeys() {
|
||||||
|
PrivateKeySupplier supplier = new PrivateKeySupplier(
|
||||||
|
Suppliers.ofInstance(new Credentials("MOMMA", "FileNotFoundCredential")),
|
||||||
|
new PrivateKeyForCredentials());
|
||||||
|
supplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCredentialsAreLoadedOnRightAlgoAndCredentials() {
|
||||||
|
Properties propertied = OAuthTestUtils.defaultProperties(new Properties());
|
||||||
|
Credentials validCredentials = new Credentials(propertied.getProperty("oauth.identity"),
|
||||||
|
propertied.getProperty("oauth.credential"));
|
||||||
|
PrivateKeySupplier supplier = new PrivateKeySupplier(Suppliers.ofInstance(validCredentials),
|
||||||
|
new PrivateKeyForCredentials());
|
||||||
|
assertNotNull(supplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrivateKey loadPrivateKey() throws Exception {
|
||||||
|
PrivateKeySupplier supplier = new PrivateKeySupplier(ofInstance(new Credentials("foo",
|
||||||
|
Files.asCharSource(new File("src/test/resources/testpk.pem"), Charsets.UTF_8).read())),
|
||||||
|
new PrivateKeyForCredentials());
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.Charsets.UTF_8;
|
||||||
|
import static com.google.common.io.BaseEncoding.base64Url;
|
||||||
|
import static org.jclouds.oauth.v2.config.PrivateKeySupplierTest.loadPrivateKey;
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@Test(groups = "unit")
|
||||||
|
public class ClaimsToAssertionTest {
|
||||||
|
|
||||||
|
private static final String PAYLOAD = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.\n" +
|
||||||
|
"eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZ" +
|
||||||
|
"GV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2ds" +
|
||||||
|
"ZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2x" +
|
||||||
|
"lLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ";
|
||||||
|
|
||||||
|
private static final String SHA256withRSA_PAYLOAD_SIGNATURE_RESULT =
|
||||||
|
"bmQrCv4gjkLWDK1JNJni74_kPiSDUMF_FImgqKJMUIgkDX1m2Sg3bH1yjF-cjBN7CvfAscnageo" +
|
||||||
|
"GtL2TGbwoTjJgUO5Yy0esavUUF-mBQHQtSw-2nL-9TNyM4SNi6fHPbgr83GGKOgA86r" +
|
||||||
|
"I9-nj3oUGd1fQty2k4Lsd-Zdkz6es";
|
||||||
|
|
||||||
|
|
||||||
|
public void sha256() throws Exception {
|
||||||
|
byte[] payloadSignature = ClaimsToAssertion.sha256(loadPrivateKey(), PAYLOAD.getBytes(UTF_8));
|
||||||
|
assertNotNull(payloadSignature);
|
||||||
|
|
||||||
|
assertEquals(base64Url().omitPadding().encode(payloadSignature), SHA256withRSA_PAYLOAD_SIGNATURE_RESULT);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<configuration scan="false">
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>target/test-data/jclouds.log</file>
|
||||||
|
|
||||||
|
<encoder>
|
||||||
|
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="WIREFILE" class="ch.qos.logback.core.FileAppender">
|
||||||
|
<file>target/test-data/jclouds-wire.log</file>
|
||||||
|
|
||||||
|
<encoder>
|
||||||
|
<Pattern>%d %-5p [%c] [%thread] %m%n</Pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root>
|
||||||
|
<level value="warn" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<logger name="org.jclouds">
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="jclouds.wire">
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<appender-ref ref="WIREFILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="jclouds.headers">
|
||||||
|
<level value="DEBUG" />
|
||||||
|
<appender-ref ref="WIREFILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -0,0 +1,15 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIICXgIBAAKBgQCwqwzakEPP+U9vx9JCuMHebFIVQZ4Sjaj2RU9dJ6YT2s3u7dC6
|
||||||
|
/0fGM5xm4fXmSHqyGC6PC8weQSkxnSpbU+R4jMWPM8ML4TIr5wP0avbg+wy3+WWI
|
||||||
|
of0MN7YHkCfqpaaiKiMw7niK1y07YvxJN8LX1xLpE7aXgIpn6L/qtJdHnQIDAQAB
|
||||||
|
AoGBAIAHlcsW3W3smPrC7sdXqXeNPHcXFH0RmC7Qz9EMmLiuyqqqQagitFsYr/GH
|
||||||
|
M3Ltd611BNi5jfUm97ly0m1ZAKp/nkTMVhfKfRIVsBDHtjQHcUOR9tr0LslptmaN
|
||||||
|
TG0bovbUohe5KwOqMK4YOjUQbInChVBrf7VrNQtv8e0eShdpAkEA3lzLP9QYfP1i
|
||||||
|
C4iYXqS7cgMDrs3qujC7PoyB54maen+Uvgyau1ZJpKMzXYkORPcYk+b71bl9jF80
|
||||||
|
U+7LDnJjPwJBAMtksvL1V8DC5DYL43497JW4KBN4YZ3K7YWx/9gkvc3Q9VdXiUGu
|
||||||
|
6WKjmcbmsPI/jFdeO71uy934N8qEXLJcyiMCQQCTNKcxWD3l8PCJZiJI9ZFKBwjX
|
||||||
|
Hmb4X+51mBsfpw7nbbKQplOBFbynC4ujrmoN6e8RaubpNGUTGqvPrNQsejmNAkEA
|
||||||
|
lUDEAH4BczaQ+QgoXI9ceVG2NvNzzrMHMcC5Ggd8MPhR0VIvKsAMC5I6WjcXSe1Q
|
||||||
|
Mxy3gf84Ix7u8fHHhCuLOQJAQRhrlXiQUk4cJumNhjza5/+KtaV4FPbEQi+qcWE6
|
||||||
|
tGoHPEBfbXyUdcUD4ae8X1W0yri0BuyVNaOCpGCBRIhPZA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
Loading…
Reference in New Issue