Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement both StaticTokenCredential and OnBehalfOfCredential for java #41

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,28 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.1.0</version>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>com.microsoft.rest</groupId>
<artifactId>client-runtime</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.8.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j;external_dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j-persistence-extension</artifactId>
<version>1.0.0</version> <!-- {x-version-update;com.microsoft.azure:msal4j-persistence-extension;external_dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure</artifactId>
Expand Down Expand Up @@ -52,6 +67,55 @@
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version> <!-- {x-version-update;junit:junit;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version> <!-- {x-version-update;org.mockito:mockito-core;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version> <!-- {x-version-update;org.powermock:powermock-module-junit4;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version> <!-- {x-version-update;org.powermock:powermock-api-mockito2;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.6.0</version> <!-- {x-version-update;net.java.dev.jna:jna-platform;external_dependency} -->
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.3.11.RELEASE</version> <!-- {x-version-update;io.projectreactor:reactor-test;external_dependency} -->
<scope>test</scope>
</dependency>
<!-- for file lock tests, ideally should be removed in the future -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version> <!-- {x-version-update;com.google.code.gson:gson;external_dependency} -->
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.linguafranca.pwdb</groupId>
<artifactId>KeePassJava2</artifactId>
<version>2.1.4</version> <!-- {x-version-update;org.linguafranca.pwdb:KeePassJava2;external_dependency} -->
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.identity.extensions;

import com.azure.core.http.HttpClient;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.ProxyOptions;
import com.azure.identity.implementation.IdentityClientOptions;

import java.time.Duration;
import java.util.Objects;
import java.util.function.Function;

/**
* The base class for all the credential builders.
* The constructor of this class is friendly, so must include it and in same folder as the extension class
* @param <T> the type of the credential builder
*/
public abstract class CredentialBuilderBase<T extends CredentialBuilderBase<T>> {
IdentityClientOptions identityClientOptions;

CredentialBuilderBase() {
this.identityClientOptions = new IdentityClientOptions();
}

/**
* Specifies the max number of retries when an authentication request fails.
*
* @param maxRetry the number of retries
* @return An updated instance of this builder with the max retry set as specified.
*/
@SuppressWarnings("unchecked")
public T maxRetry(int maxRetry) {
this.identityClientOptions.setMaxRetry(maxRetry);
return (T) this;
}

/**
* Specifies a Function to calculate seconds of timeout on every retried request.
*
* @param retryTimeout the Function that returns a timeout in seconds given the number of retry
* @return An updated instance of this builder with the retry timeout set as specified.
*/
@SuppressWarnings("unchecked")
public T retryTimeout(Function<Duration, Duration> retryTimeout) {
this.identityClientOptions.setRetryTimeout(retryTimeout);
return (T) this;
}


/**
* Specifies the options for proxy configuration.
*
* @deprecated Configure the proxy options on the {@link HttpClient} instead and then set that
* client on the credential using {@link #httpClient(HttpClient)}.
*
* @param proxyOptions the options for proxy configuration
* @return An updated instance of this builder with the proxy options set as specified.
*/
@Deprecated
@SuppressWarnings("unchecked")
public T proxyOptions(ProxyOptions proxyOptions) {
this.identityClientOptions.setProxyOptions(proxyOptions);
return (T) this;
}

/**
* Specifies the HttpPipeline to send all requests. This setting overrides the others.
*
* @param httpPipeline the HttpPipeline to send all requests
* @return An updated instance of this builder with the http pipeline set as specified.
*/
@SuppressWarnings("unchecked")
public T httpPipeline(HttpPipeline httpPipeline) {
this.identityClientOptions.setHttpPipeline(httpPipeline);
return (T) this;
}

/**
* Sets the HTTP client to use for sending and receiving requests to and from the service.
*
* @param client The HTTP client to use for requests.
* @return An updated instance of this builder with the http client set as specified.
* @throws NullPointerException If {@code client} is {@code null}.
*/
@SuppressWarnings("unchecked")
public T httpClient(HttpClient client) {
Objects.requireNonNull(client);
this.identityClientOptions.setHttpClient(client);
return (T) this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.azure.identity.extensions;

import com.azure.core.annotation.Immutable;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.util.logging.ClientLogger;
import com.azure.identity.extensions.implementation.IdentityClient;
import com.azure.identity.extensions.implementation.IdentityClientBuilder;
import com.azure.identity.implementation.util.LoggingUtil;
import com.microsoft.aad.msal4j.UserAssertion;
import reactor.core.publisher.Mono;

/**
* An AAD credential that via the On Behalf Of flow for an AAD application.
*/
@Immutable
public class OnBehalfOfFlowCredential implements TokenCredential {
private final IdentityClient identityClient;
private final UserAssertion userAssertion;
private final ClientLogger logger = new ClientLogger(StaticTokenCredential.class);

/**
* Creates a OnBehalfOfCredential
*
* @param tokenString The string of prefetched token
* @param accessToken The prefetched token
*/
OnBehalfOfFlowCredential(String tenantId, String clientId,
String clientSecret, String tokenString,
AccessToken accessToken) {
identityClient = new IdentityClientBuilder()
.tenantId(tenantId)
.clientId(clientId)
.clientSecret(clientSecret)
.build();
userAssertion = new UserAssertion(tokenString != null ? tokenString : accessToken.getToken());
}

@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
return identityClient.authenticateWithOnBehalfOfCredentialCache(request, this.userAssertion)
.onErrorResume(t -> Mono.empty())
.switchIfEmpty(Mono.defer(() -> identityClient.authenticateWithOnBehalfOfCredential(request, this.userAssertion)))
.doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
.doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.azure.identity.extensions;

import com.azure.core.credential.AccessToken;
import com.azure.identity.extensions.implementation.util.ValidationUtil;

import java.util.HashMap;

/**
* Fluent credential builder for instantiating a {@link OnBehalfOfFlowCredential}.
*
* @see OnBehalfOfFlowCredential
*/
public class OnBehalfOfFlowCredentialBuilder extends CredentialBuilderBase<OnBehalfOfFlowCredentialBuilder> {

private String tenantId;

private String clientId;

private String clientSecret;

private String tokenString;

private AccessToken accessToken;

public OnBehalfOfFlowCredentialBuilder tenantId(String tenantId){
this.tenantId = tenantId;
return this;
}

/**
* Sets the prefetched token string for the token.
*
* @param clientId The id of the client
*
* @return The updated OnBehalfOfCredentialBuilder object.
*/
public OnBehalfOfFlowCredentialBuilder clientId(String clientId) {
this.clientId = clientId;
return this;
}

/**
* Sets the prefetched token string for the token.
*
* @param clientSecret The secret of the client
*
* @return The updated OnBehalfOfCredentialBuilder object.
*/
public OnBehalfOfFlowCredentialBuilder clientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}


/**
* Sets the On behalf of Flow token string for the token.
*
* @param tokenString The On behalf of Flow token string of prefetched token
*
* @return The updated OnBehalfOfCredentialBuilder object.
*/
public OnBehalfOfFlowCredentialBuilder tokenString(String tokenString) {
this.tokenString = tokenString;
return this;
}

/**
* Sets the On behalf of Flow token for the token.
*
* @param accessToken The On behalf of Flow token of prefetched token
*
* @return The updated OnBehalfOfCredentialBuilder object.
*/
public OnBehalfOfFlowCredentialBuilder accessToken(AccessToken accessToken) {
this.accessToken = accessToken;
return this;
}

public OnBehalfOfFlowCredential build() {
com.azure.identity.implementation.util.ValidationUtil.validate(getClass().getSimpleName(), new HashMap<String, Object>() {{
put("tenantId", tenantId);
put("clientId", clientId);
put("clientSecret", clientSecret);
}});
ValidationUtil.validateAllEmpty(getClass().getSimpleName(), new HashMap<String, Object>() {{
put("tokenString", tokenString);
put("accessToken", accessToken);
}});
return new OnBehalfOfFlowCredential(tenantId, clientId, clientSecret, tokenString, accessToken);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.azure.identity.extensions;

import com.azure.core.annotation.Immutable;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenCredential;
import com.azure.core.credential.TokenRequestContext;
import reactor.core.publisher.Mono;

import java.time.OffsetDateTime;

/**
* An AAD credential with a prefetched token for an AAD application.
*/
@Immutable
public class StaticTokenCredential implements TokenCredential {
private final AccessToken accessToken;

/**
* Creates a StaticTokenCredential
*
* @param tokenString The string of prefetched token
* @param accessToken The prefetched token
*/
StaticTokenCredential(String tokenString, AccessToken accessToken) {
this.accessToken = new AccessToken(
tokenString != null ? tokenString : accessToken.getToken(),
tokenString != null ? OffsetDateTime.MIN : accessToken.getExpiresAt()
);
}

@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
return Mono.just(this.accessToken);
}
}
Loading