Skip to content
Closed
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
03f8ab4
In progress
mnriem Aug 5, 2020
7266ac5
Throw an exception when configuration is incomplete
mnriem Aug 6, 2020
0de9b40
Added class level JavaDoc
mnriem Aug 6, 2020
3964b68
Resolve POM conflict
mnriem Aug 6, 2020
9fb49ba
Added unit tests
mnriem Aug 6, 2020
9acd2b9
Merge branch 'credential-helper' of https://github.com/mnriem/azure-s…
yevster Aug 12, 2020
bedcf50
Changing the Identity Helper into a builder
yevster Aug 13, 2020
9ad8a79
Adding storage resource sample, autoconfiguration for blob storage wi…
yevster Aug 15, 2020
f9adbe0
Attempting to generate an endpoint string
yevster Aug 17, 2020
9cac0d3
Provisional workaround for using IdentityToken in Shared service conn…
yevster Aug 21, 2020
c2bbb46
Merge branch 'master' into storage-identity
yevster Aug 22, 2020
41f32fa
In progress
mnriem Aug 5, 2020
8c1297b
Throw an exception when configuration is incomplete
mnriem Aug 6, 2020
f0322af
Added class level JavaDoc
mnriem Aug 6, 2020
8a6385c
Resolve POM conflict
mnriem Aug 6, 2020
9305ee1
Added unit tests
mnriem Aug 6, 2020
92fd3dd
Changing the Identity Helper into a builder
yevster Aug 13, 2020
06d8aa9
Adding storage resource sample, autoconfiguration for blob storage wi…
yevster Aug 15, 2020
723e463
Attempting to generate an endpoint string
yevster Aug 17, 2020
62c5533
Provisional workaround for using IdentityToken in Shared service conn…
yevster Aug 21, 2020
b96c739
Merge branch 'storage-identity' of github.com:yevster/azure-sdk-for-j…
yevster Sep 14, 2020
3f7e8ac
XML syntax fix
yevster Sep 14, 2020
670e4a2
Storage resource demo works
yevster Sep 17, 2020
cfef365
Removing tight Resource Manager Provider coupling
yevster Sep 30, 2020
f7f9414
Merge branch 'master' into storage-identity-remove-resource-manager-p…
yevster Oct 2, 2020
9662b85
Fixing autoconfiguration for legacy resource management to not kick i…
yevster Oct 3, 2020
6f780a9
Merge branch 'master' into storage-identity-remove-resource-manager-p…
yevster Oct 8, 2020
ef3f807
Resource demo works with file legacy file credentail'
yevster Oct 15, 2020
525c5b4
Resource sample works with environment parameters
yevster Oct 15, 2020
7d7adb1
Removing debugging code
yevster Oct 15, 2020
80a9e20
Fixing SPEL expression to omit legacy auto-config when using Spring e…
yevster Oct 15, 2020
9277d71
checkstyle appeasement
yevster Oct 17, 2020
e4608d5
Checktyle appeasement, version fixing
yevster Oct 19, 2020
42d5e50
Adding mock token credentail to Actuator test config to ward off unea…
yevster Oct 20, 2020
29beabc
Adding mock token credentail to Actuator test config to ward off unea…
yevster Oct 21, 2020
969353e
Removing topic manager dependency from queue autoconfig
yevster Oct 22, 2020
bbc811e
Fixing autoconfig dependencies for Topic binders
yevster Oct 22, 2020
01b1378
Fixing startup failure when namespace is provided instead of connection
yevster Oct 28, 2020
cc74b40
Updating to incorporate upstream library signature changes
yevster Oct 28, 2020
cdb5ed4
Merge branch 'master' into storage-identity-remove-resource-manager-p…
saragluna Oct 28, 2020
bccd6e2
include version tag should be on the same line
saragluna Oct 28, 2020
9a206f1
Spring Cloud Stream - Event Hub binder demo now works
yevster Oct 28, 2020
f19af87
merging with master
yevster Oct 29, 2020
7bec935
Fixing build failures
yevster Oct 29, 2020
456d383
Preventing reliance on resource management bean when resource group
yevster Oct 30, 2020
9b1792b
Merge branch 'master' into storage-identity-remove-resource-manager-p…
yevster Oct 30, 2020
fcb8bc8
Fixing NPE in auto-configuration
yevster Oct 30, 2020
a6cfcfe
Fixing NPE in auto-configuration
yevster Oct 30, 2020
8277c89
Fixing package names broken in merge
yevster Oct 30, 2020
005e67f
Checkstyle appeasement
yevster Oct 30, 2020
552af0c
Checkstyle appeasement
yevster Oct 30, 2020
2456b26
Merge branch 'context-config-npe-fix' into storage-identity-remove-re…
yevster Oct 30, 2020
8558375
Eliminating ServiceBus ARM dependency when not pulled in thorugh sett…
yevster Oct 31, 2020
dde0594
Deleting duplicate sample
yevster Oct 31, 2020
2a7d5a3
Removing module for superfluous storage resource demeo
yevster Nov 2, 2020
5531c84
Fixing parent pom of identity library
yevster Nov 2, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.servicebus.stream.binder.config.ServiceBusTopicBinderConfiguration.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.servicebus.stream.binder.config.ServiceBusQueueBinderConfiguration.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.eventhub.stream.binder.config.EventHubBinderConfiguration.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.cloud.autoconfigure.context.AzureResourceManager20AutoConfiguration.java"/>

<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.data.gremlin.conversion.source.AbstractGremlinSource.java"/>
<suppress checks="com.azure.tools.checkstyle.checks.EnforceFinalFieldsCheck" files="com.azure.spring.keyvault.KeyVaultOperation.java"/>
Expand Down
69 changes: 69 additions & 0 deletions sdk/spring/azure-identity-spring-library/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>

<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.azure</groupId>
<artifactId>azure-spring-boot-service</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>azure-identity-spring-library</artifactId>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest change the artifact id to azure-identity-spring

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mnriem, any objection to eliminating the Spring Environment identity library entirely and moving this functionality to the azure-spring-cloud-context module?

<packaging>jar</packaging>
<name>Azure Identity Spring Integration Library</name>
<version>1.0.0-beta.1</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>4.13</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.azure.identity.spring;

import java.util.HashMap;
import java.util.Optional;

import org.springframework.core.env.Environment;

import com.azure.core.credential.TokenCredential;
import com.azure.identity.ChainedTokenCredential;
import com.azure.identity.ChainedTokenCredentialBuilder;
import com.azure.identity.ClientCertificateCredentialBuilder;
import com.azure.identity.ClientSecretCredentialBuilder;
import com.azure.identity.DefaultAzureCredentialBuilder;

/**
* A helper class to deal with credentials in a Spring environment.
*
* <p>
* This helper class makes it possible to configure credentials to be used
* within a Spring context.
* </p>
*
* <table>
* <tr>
* <th>Property Tuples</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>azure.credential.(name.)tenantId <br>
* azure.credential.(name.)clientId <br>
* azure.credential.(name.)clientSecret</td>
* <td>the Azure Tenant ID <br>
* the Client ID <br>
* the Client Certificate <br>
* </td>
* </tr>
* <tr>
* <td>azure.credential.(name.)tenantId <br>
* azure.credential.(name.)clientId <br>
* azure.credential.(name.)clientCertificate</td>
* <td>the Azure Tenant ID <br>
* the Client ID <br>
* the path to the PEM client certificate</td>
* </tr>
* </table>
*
* where name is the <code>name</code> of the credential. Note if
* <code>name</code> is entirely omitted it is taken to be the default
* credential. Note if the default credential is omitted it is configure to use
* AzureDefaultCredential which allows for the use a Managed Identity (if it is
* present).
*
* @author manfred.riem@microsoft.com
*/
public class SpringEnvironmentTokenBuilder {

/**
* Defines the AZURE_CREDENTIAL_PREFIX.
*/
private static final String AZURE_CREDENTIAL_PREFIX = "azure.credential.";

/**
* Stores the named credentials.
*/
private final HashMap<String, TokenCredential> credentials;

/**
* Stores the name of the credential to be returned. If omitted, the default
* credential will be returned.
*/
private String name = "";

/**
* Constructor.
*/
public SpringEnvironmentTokenBuilder() {
credentials = new HashMap<>();
credentials.put("", new DefaultAzureCredentialBuilder().build());
}

/**
* Populate from Environment.
*
* @param environment the environment.
*/
public SpringEnvironmentTokenBuilder fromEnvironment(Environment environment) {
populateNamedCredential(environment, "");
String credentialNamesKey = AZURE_CREDENTIAL_PREFIX + "names";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to consider make use of @ConfigurationProperties and model different credentials as Map, so that we don't need the 'names' here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a repackaging of @mnriem's code, so perhaps he can answer better. My understanding is that a custom map is needed because property values could come from multiple sources, and this serves to consolidate them in a single place.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'names' defines the order of the chain.

if (environment.containsProperty(credentialNamesKey)) {
String[] credentialNames = environment.getProperty(credentialNamesKey).split(",");
for (int i = 0; i < credentialNames.length; i++) {
populateNamedCredential(environment, credentialNames[i]);
}
}
return this;
}

/**
* Sets a credential to override a named credential. If this credential fails to produce a token,
* the original token credential will be used.
*
* @param name
* @param credential
* @return
*/
public SpringEnvironmentTokenBuilder overrideNamedCredential(String name, TokenCredential credential) {
TokenCredential currentCredential = credentials.get(name);
if (currentCredential == null) {
credentials.put(name, credential);
} else {
ChainedTokenCredentialBuilder builder = new ChainedTokenCredentialBuilder();
builder.addFirst(credential);
builder.addLast(currentCredential);
credentials.put(name, builder.build());
}

return this;
}

/**
* Populate a named credential.
*
* @param environment the environment
* @param name the name.
*/
private void populateNamedCredential(Environment environment, String name) {
String standardizedName = name;

if (!standardizedName.equals("") && !standardizedName.endsWith(".")) {
standardizedName = standardizedName + ".";
}

String tenantIdKey = AZURE_CREDENTIAL_PREFIX + standardizedName + "tenantId";
String clientIdKey = AZURE_CREDENTIAL_PREFIX + standardizedName + "clientId";
String clientSecretKey = AZURE_CREDENTIAL_PREFIX + standardizedName + "clientSecret";

String tenantId = environment.getProperty(tenantIdKey);
String clientId = environment.getProperty(clientIdKey);
String clientSecret = environment.getProperty(clientSecretKey);

if (tenantId != null && clientId != null && clientSecret != null) {
TokenCredential credential = new ClientSecretCredentialBuilder().tenantId(tenantId).clientId(clientId)
.clientSecret(clientSecret).build();
credentials.put(name, credential);
return;
}

String clientCertificateKey = AZURE_CREDENTIAL_PREFIX + standardizedName + "clientCertificate";
String clientCertificatePath = environment.getProperty(clientCertificateKey);

if (tenantId != null && clientId != null && clientCertificatePath != null) {
TokenCredential credential = new ClientCertificateCredentialBuilder().tenantId(tenantId).clientId(clientId)
.pemCertificate(clientCertificatePath).build();
credentials.put(name, credential);
return;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For system assigned identity, tenant id is needed.
For user assigned identity, tenant id and one optional client id is needed.

Code here has some problems work with system assigned identity and user assigned identity.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to always fall back to DefaultAzureCredentialBuilder?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I defer to @mnriem on this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For system assigned you do not need tenant id. User-assigned was not targeted and would need to be added if required.

if (!name.equals("")) {
throw new IllegalStateException("Configuration for azure.credential." + name + " is incomplete");
}
}

/**
* Sets the builder to return a credential named <code>name</code>
*
* @param name
* @return
*/
public SpringEnvironmentTokenBuilder namedCredential(String name) {
this.name = name;
return this;
}

/**
* Sets the builder to return the default credential.
*/
public SpringEnvironmentTokenBuilder defaultCredential() {
return namedCredential("");
}

/**
* Builds an Azure TokenCredendial.
*
* @throws IllegalArgumentException if attempting to retrieve a named credential
* not defined in the environment.
*/
public TokenCredential build() {
TokenCredential result = credentials.get(name);
if (result == null) {
throw new IllegalArgumentException(
"Attempting to retrieve Azure credential not configured in the environment. (name=" + name + ")");
} else {
return result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.azure.identity.spring;

import com.azure.identity.ClientSecretCredential;
import com.azure.identity.DefaultAzureCredential;
import com.azure.identity.DefaultAzureCredentialBuilder;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.StandardEnvironment;

/**
* The unit tests for the AzureIdentitySpringHelper class.
*
* @author manfred.riem@microsoft.com
*/
public class SpringEnvironmentTokenBuilderTest {

/**
* Test getDefaultCredential method.
*/
@Test
public void testGetDefaultCredential() {
SpringEnvironmentTokenBuilder builder = new SpringEnvironmentTokenBuilder();
assertNotNull(builder.build());
assertEquals(builder.build(), builder.defaultCredential().build());
}

/**
* Test populate method.
*/
@Test
public void testPopulate() {
System.setProperty("azure.credential.names", "");
System.setProperty("azure.credential.tenantId", "tenantId");
System.setProperty("azure.credential.clientId", "clientId");
System.setProperty("azure.credential.clientSecret", "clientSecret");
StandardEnvironment environment = new StandardEnvironment();
SpringEnvironmentTokenBuilder builder = new SpringEnvironmentTokenBuilder();
builder.fromEnvironment(environment);

assertNotNull(builder.build());
assertTrue(builder.build() instanceof ClientSecretCredential);
assertEquals(builder.build(), builder.defaultCredential().build());
}

/**
* Test populate method.
*/
@Test
public void testPopulate2() {
System.setProperty("azure.credential.names", "myname");
System.setProperty("azure.credential.myname.tenantId", "tenantId");
System.setProperty("azure.credential.myname.clientId", "clientId");
System.setProperty("azure.credential.myname.clientSecret", "clientSecret");
StandardEnvironment environment = new StandardEnvironment();
SpringEnvironmentTokenBuilder builder = new SpringEnvironmentTokenBuilder();
builder.fromEnvironment(environment);
assertNotNull(builder.namedCredential("myname").build());
assertTrue(builder.build() instanceof ClientSecretCredential);
assertNotEquals(builder.build(), builder.defaultCredential().build());
}

/**
* Test populate method.
*/
@Test
public void testPopulate3() {
System.setProperty("azure.credential.names", "myname2");
System.setProperty("azure.credential.myname2.tenantId", "tenantId");
System.setProperty("azure.credential.myname2.clientSecret", "clientSecret");
StandardEnvironment environment = new StandardEnvironment();
SpringEnvironmentTokenBuilder builder = new SpringEnvironmentTokenBuilder();
try {
builder.fromEnvironment(environment);
fail();
} catch (Throwable t) {
assertEquals(IllegalStateException.class, t.getClass(),
"Unexpected exception class on missing configuration field.");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use assertThrows() here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not to use Exception annotations for generic exceptions (e.g. IllegalStateException). Because these can be thrown from just about anywhere, it's important to ensure that it gets thrown from a particular place, not just anywhere in the test method.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-servicebus</artifactId>
<version>7.0.0-beta.7</version> <!-- {x-version-update;com.azure:azure-messaging-servicebus;current} -->
<version>7.0.0-beta.6</version> <!-- {x-version-update;com.azure:azure-messaging-servicebus;current} -->
</dependency>
</dependencies>

Expand Down
Loading