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

Tenant Profiles #263

Merged
merged 26 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
36c8796
Merge pull request #245 from AzureAD/dev
SomkaPe Jun 3, 2020
a60641b
Merge pull request #249 from AzureAD/dev
SomkaPe Jun 10, 2020
0274b05
Classes for tenant profile functionality
Avery-Dunn Jul 21, 2020
7412180
Implement tenant profile feature
Avery-Dunn Jul 21, 2020
2982cb4
Tests for tenant profile feature
Avery-Dunn Jul 21, 2020
3f746c0
Merge branches 'avdunn/tenant-profiles' and 'dev' of https://github.c…
Avery-Dunn Aug 12, 2020
c7c53dc
Simplify tenant profile class structure
Avery-Dunn Aug 12, 2020
f92e166
1.6.2 release
SomkaPe Aug 17, 2020
00c920c
Classes for tenant profile redesign
Avery-Dunn Aug 21, 2020
42a8fc7
Tests for tenant profile redesign
Avery-Dunn Aug 21, 2020
7a0dfa4
Adjust sample cached ID tokens to have realistic headers
Avery-Dunn Aug 21, 2020
bd0cc29
Redesign how Tenant Pofiles are added to Accounts
Avery-Dunn Aug 21, 2020
9254615
New error code for JWT parse exceptions
Avery-Dunn Aug 21, 2020
1f898f6
Add claims and tenant profiles fields to Account
Avery-Dunn Aug 21, 2020
0e49632
Remove annotation excluding realm field from comparisons
Avery-Dunn Aug 21, 2020
49a3da9
Merge branch 'pesomka/release.1.6.2' of https://github.com/AzureAD/mi…
Avery-Dunn Aug 21, 2020
54341a6
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
Avery-Dunn Aug 21, 2020
ed16f24
Use more generic token
Avery-Dunn Aug 21, 2020
f92983d
Remove ID token claims field from Account
Avery-Dunn Aug 25, 2020
1f66818
Minor changes for clarity
Avery-Dunn Aug 25, 2020
36e2638
Adjust tests for tenant profile design refactor
Avery-Dunn Aug 25, 2020
8424a43
Refactor tenant profile structure
Avery-Dunn Aug 25, 2020
f778f31
Minor fixes
Avery-Dunn Aug 25, 2020
ae56c04
Minor fixes
Avery-Dunn Aug 25, 2020
9a4b4f2
Minor fixes
Avery-Dunn Aug 25, 2020
d9fbf46
Simplify tenant profile class
Avery-Dunn Aug 31, 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
19 changes: 19 additions & 0 deletions src/main/java/com/microsoft/aad/msal4j/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.Map;

/**
* Representation of a single user account. If modifying this object, ensure it is compliant with
Expand All @@ -23,4 +24,22 @@ class Account implements IAccount {
String environment;

String username;

String localAccountId;
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved

Map<String, ?> idTokenClaims;
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved

Map<String, IAccount> tenantProfiles;

public String getTenantId() {
return localAccountId;
}

public Map<String, ?> getClaims() {
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
return idTokenClaims;
}

public Map<String, IAccount> getTenantProfiles() {
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
return tenantProfiles;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Accessors(fluent = true)
@Getter
Expand Down Expand Up @@ -49,6 +50,8 @@ ClientInfo clientInfo() {
@JsonProperty("authority_type")
protected String authorityType;

protected Map<String, ?> idTokenClaims;
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved

String getKey() {

List<String> keyParts = new ArrayList<>();
Expand Down Expand Up @@ -77,6 +80,7 @@ static AccountCacheEntity create(String clientInfoStr, Authority requestAuthorit
account.localAccountId(localAccountId);
account.username(idToken.preferredUsername);
account.name(idToken.name);
account.idTokenClaims(idToken.tokenClaims());
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
}

return account;
Expand All @@ -101,6 +105,6 @@ static AccountCacheEntity create(String clientInfoStr, Authority requestAuthorit
}

IAccount toAccount(){
return new Account(homeAccountId, environment, username);
return new Account(homeAccountId, environment, username, localAccountId, idTokenClaims, null);
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/microsoft/aad/msal4j/IAccount.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package com.microsoft.aad.msal4j;

import java.util.Map;
import java.util.Set;

/**
Expand All @@ -26,4 +27,19 @@ public interface IAccount {
* @return account username
*/
String username();

/**
* @return claims in id token
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
*/
Map<String, ?> getClaims();

/**
* @return tenant id
*/
String getTenantId();

/**
* @return tenant profiles
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
*/
Map<String, IAccount> getTenantProfiles();
}
25 changes: 25 additions & 0 deletions src/main/java/com/microsoft/aad/msal4j/IdToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.nimbusds.jwt.JWTClaimsSet;

import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

class IdToken {

Expand Down Expand Up @@ -59,6 +61,29 @@ class IdToken {
@JsonProperty("unique_name")
protected String uniqueName;

/**
* Used to attach all claims in an ID token to an account object
*
* @return map of key/value pairs of claims in ID token
*/
protected Map<String, Object> tokenClaims() {
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
Map<String, Object> idTokenClaims = new HashMap<>();
idTokenClaims.put(ISSUER, this.issuer);
idTokenClaims.put(SUBJECT, this.subject);
idTokenClaims.put(AUDIENCE, this.audience);
idTokenClaims.put(EXPIRATION_TIME, this.expirationTime);
idTokenClaims.put(ISSUED_AT, this.issuedAt);
idTokenClaims.put(NOT_BEFORE, this.notBefore);
idTokenClaims.put(NAME, this.name);
idTokenClaims.put(PREFERRED_USERNAME, this.preferredUsername);
idTokenClaims.put(OBJECT_IDENTIFIER, this.objectIdentifier);
idTokenClaims.put(TENANT_IDENTIFIER, this.tenantIdentifier);
idTokenClaims.put(UPN, this.upn);
idTokenClaims.put(UNIQUE_NAME, this.uniqueName);

return idTokenClaims;
}

static IdToken createFromJWTClaims(final JWTClaimsSet claims) throws ParseException {
IdToken idToken = new IdToken();

Expand Down
45 changes: 44 additions & 1 deletion src/main/java/com/microsoft/aad/msal4j/TokenCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,53 @@ Set<IAccount> getAccounts(String clientId, Set<String> environmentAliases) {
build())) {
try {
lock.readLock().lock();
Set<IAccount> rootAccounts = new HashSet<>();

return accounts.values().stream().
//Filter accounts map into two sets: a set of home accounts (local UID = home UID),
// and a set of tenant profiles (local UID != home UID)
Set<IAccount> homeAccounts = accounts.values().stream().
filter(acc -> environmentAliases.contains(acc.environment())).
filter(acc -> acc.homeAccountId().contains(acc.localAccountId())).
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
collect(Collectors.mapping(AccountCacheEntity::toAccount, Collectors.toSet()));
Set<IAccount> tenantAccounts = accounts.values().stream().
filter(acc -> environmentAliases.contains(acc.environment())).
filter(acc -> !acc.homeAccountId().contains(acc.localAccountId())).
collect(Collectors.mapping(AccountCacheEntity::toAccount, Collectors.toSet()));

Iterator<IAccount> homeAcctsIterator = homeAccounts.iterator();
Iterator<IAccount> tenantAcctsIterator;
Map<String, IAccount> tenantProfiles;
Account homeAcc, tenantAcc;

while (homeAcctsIterator.hasNext()) {
homeAcc = (Account) homeAcctsIterator.next();
tenantAcctsIterator = tenantAccounts.iterator();
tenantProfiles = new HashMap<>();

while (tenantAcctsIterator.hasNext()) {
tenantAcc = (Account) tenantAcctsIterator.next();

//If the tenant account's home ID matches a home ID (UID) in the list of home accounts, it is a
//tenant profile of that home account
if (tenantAcc.homeAccountId().contains(homeAcc.homeAccountId().substring(0, homeAcc.homeAccountId().indexOf(".")))) {
tenantProfiles.put(tenantAcc.getTenantId(), tenantAcc);
tenantAcctsIterator.remove();
}
}
if (tenantProfiles.isEmpty()) {
//If the home account has no tenant profiles, leave it as an Account object
rootAccounts.add(homeAcc);
} else {
//If the home account had some tenant profiles, transform the home Account object into a
//MultiTenantAccount object and attach its tenant profile list
homeAcc.tenantProfiles(tenantProfiles);
rootAccounts.add(homeAcc);
}
}
//Add any tenant accounts which did not have a corresponding home account to the list of root accounts
rootAccounts.addAll(tenantAccounts);

return rootAccounts;
Avery-Dunn marked this conversation as resolved.
Show resolved Hide resolved
} finally {
lock.readLock().unlock();
}
Expand Down
50 changes: 50 additions & 0 deletions src/test/java/com/microsoft/aad/msal4j/AccountTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.aad.msal4j;

import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Iterator;
import java.util.Map;

public class AccountTest {

@Test
public void testMultiTenantAccount_AccessTenantProfile() throws IOException, URISyntaxException {

ITokenCacheAccessAspect accountCache = new CachePersistenceIT.TokenPersistence(
TestHelper.readResource(this.getClass(),
"/cache_data/multi-tenant-account-cache.json"));

PublicClientApplication app = PublicClientApplication.builder("uid1")
.setTokenCacheAccessAspect(accountCache).build();

Assert.assertEquals(app.getAccounts().join().size(), 3);
Iterator<IAccount> acctIterator = app.getAccounts().join().iterator();

IAccount curAccount;
while (acctIterator.hasNext()) {
curAccount = acctIterator.next();

if (curAccount.username().equals("MultiTenantAccount")) {
Assert.assertEquals(curAccount.homeAccountId(), "uid1.utid1");
Map<String, IAccount> tenantProfiles = curAccount.getTenantProfiles();
Assert.assertNotNull(tenantProfiles);
Assert.assertEquals(tenantProfiles.size(), 2);
Assert.assertNotNull(tenantProfiles.get("utid2"));
Assert.assertEquals(tenantProfiles.get("utid2").username(), "TenantProfile1");
Assert.assertEquals(tenantProfiles.get("utid2").username(), "TenantProfile1");
Assert.assertNotNull(tenantProfiles.get("utid3"));
Assert.assertEquals(tenantProfiles.get("utid3").username(), "TenantProfile2");
}
else if (curAccount.username().equals("TenantProfileNoHome") ||
curAccount.username().equals("SingleTenantAccount") ) {
Map<String, IAccount> tenantProfiles = curAccount.getTenantProfiles();
Assert.assertNull(tenantProfiles);
}
}
}
}
45 changes: 45 additions & 0 deletions src/test/resources/cache_data/multi-tenant-account-cache.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"Account": {
"uid1.utid1-login.windows.net-contoso": {
"username": "MultiTenantAccount",
"local_account_id": "utid1",
"realm": "contoso",
"environment": "login.windows.net",
"home_account_id": "uid1.utid1",
"authority_type": "MSSTS"
},
"uid1.utid2-login.windows.net-contoso": {
"username": "TenantProfile1",
"local_account_id": "utid2",
"realm": "contoso",
"environment": "login.windows.net",
"home_account_id": "uid1.utid1",
"authority_type": "MSSTS"
},
"uid1.utid3-login.windows.net-contoso": {
"username": "TenantProfile2",
"local_account_id": "utid3",
"realm": "contoso",
"environment": "login.windows.net",
"home_account_id": "uid1.utid1",
"authority_type": "MSSTS"
},
"uid2.utid4-login.windows.net-contoso": {
"username": "TenantProfileNoHome",
"local_account_id": "utid4",
"realm": "contoso",
"environment": "login.windows.net",
"home_account_id": "uid2.utid2",
"authority_type": "MSSTS"
},
"uid3.utid5-login.windows.net-contoso": {
"username": "SingleTenantAccount",
"local_account_id": "uid5",
"realm": "contoso",
"environment": "login.windows.net",
"home_account_id": "uid3.utid3",
"authority_type": "MSSTS"
}
},
"AppMetadata": {}
}