diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4b3b2513..61fdf531 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,12 @@ # Authentication Service MKII release notes +## 0.8.1 + +* Agent, Developer, and Service tokens will now inherit the MFA status of the Login token used + to create them. +* Replaced the deprecated Globus `identities_set` directive with `identity_set`. + See https://docs.globus.org/api/auth/reference/#token-introspect + ## 0.8.0 * BACKWARDS INCOMPATIBILITY: In flight login sessions when the server is upgraded will fail. diff --git a/src/main/java/us/kbase/auth2/Version.java b/src/main/java/us/kbase/auth2/Version.java index 2e472882..21f71bec 100644 --- a/src/main/java/us/kbase/auth2/Version.java +++ b/src/main/java/us/kbase/auth2/Version.java @@ -5,6 +5,6 @@ public class Version { /** The version of the KBase Auth2 service. */ - public static final String VERSION = "0.8.0"; + public static final String VERSION = "0.8.1"; } diff --git a/src/main/java/us/kbase/auth2/lib/Authentication.java b/src/main/java/us/kbase/auth2/lib/Authentication.java index 0c1662da..3dccb7f7 100644 --- a/src/main/java/us/kbase/auth2/lib/Authentication.java +++ b/src/main/java/us/kbase/auth2/lib/Authentication.java @@ -892,8 +892,11 @@ public NewToken createToken( throw new IllegalArgumentException("Cannot create a login token without logging in"); } // check for disabled user for all token type targets as well - final AuthUser au = getUser(token, - new OpReqs("create {} token", tokenType.getDescription()).types(TokenType.LOGIN)); + final UserAndToken uat = getUserAndToken( + token, + new OpReqs("create {} token", tokenType.getDescription()).types(TokenType.LOGIN) + ); + final AuthUser au = uat.user; if (!TokenType.AGENT.equals(tokenType)) { final Role reqRole = TokenType.SERV.equals(tokenType) ? Role.SERV_TOKEN : Role.DEV_TOKEN; @@ -910,6 +913,7 @@ public NewToken createToken( .withLifeTime(clock.instant(), life) .withContext(tokenCtx) .withTokenName(tokenName) + .withMFA(uat.token.getMFA()) .build(), randGen.getToken() ); @@ -953,8 +957,27 @@ private AuthUser getUser( final IncomingToken token, final OpReqs reqs) throws AuthStorageException, InvalidTokenException, UnauthorizedException { - final StoredToken ht = getToken(token, reqs); - final AuthUser u = getUser(ht.getUserName()); + return getUserAndToken(token, reqs).user; + } + + private static class UserAndToken { + public final AuthUser user; + public final StoredToken token; + + private UserAndToken(final AuthUser user, final StoredToken token) { + super(); + this.user = user; + this.token = token; + } + } + + // requires the user to have at least one of the required roles + private UserAndToken getUserAndToken( + final IncomingToken token, + final OpReqs reqs) + throws AuthStorageException, InvalidTokenException, UnauthorizedException { + final StoredToken st = getToken(token, reqs); + final AuthUser u = getUser(st.getUserName()); if (reqs.requiredRoles.size() > 0) { final Set has = u.getRoles().stream().flatMap(r -> r.included().stream()) .collect(Collectors.toSet()); @@ -964,7 +987,7 @@ private AuthUser getUser( throw new UnauthorizedException(); } } - return u; + return new UserAndToken(u, st); } // assumes that the token has already been checked and is valid for this user. diff --git a/src/main/java/us/kbase/auth2/providers/GlobusIdentityProviderFactory.java b/src/main/java/us/kbase/auth2/providers/GlobusIdentityProviderFactory.java index d1e0a679..28e07453 100644 --- a/src/main/java/us/kbase/auth2/providers/GlobusIdentityProviderFactory.java +++ b/src/main/java/us/kbase/auth2/providers/GlobusIdentityProviderFactory.java @@ -226,7 +226,7 @@ private Idents getPrimaryIdentity( final MultivaluedMap formParameters = new MultivaluedHashMap<>(); formParameters.add("token", accessToken); if (!ignoreSecondaries) { - formParameters.add("include", "identities_set"); + formParameters.add("include", "identity_set"); } final Map m; @@ -256,7 +256,7 @@ private Idents getPrimaryIdentity( new RemoteIdentityID(NAME, id), new RemoteIdentityDetails(username, name, email)); @SuppressWarnings("unchecked") - List secids = (List) m.get("identities_set"); + List secids = (List) m.get("identity_set"); if (secids == null) { secids = Collections.emptyList(); } diff --git a/src/test/java/us/kbase/test/auth2/lib/AuthenticationTokenTest.java b/src/test/java/us/kbase/test/auth2/lib/AuthenticationTokenTest.java index faf6a615..6c583efc 100644 --- a/src/test/java/us/kbase/test/auth2/lib/AuthenticationTokenTest.java +++ b/src/test/java/us/kbase/test/auth2/lib/AuthenticationTokenTest.java @@ -46,6 +46,7 @@ import us.kbase.auth2.lib.exceptions.UnauthorizedException; import us.kbase.auth2.lib.storage.AuthStorage; import us.kbase.auth2.lib.token.IncomingToken; +import us.kbase.auth2.lib.token.MFAStatus; import us.kbase.auth2.lib.token.NewToken; import us.kbase.auth2.lib.token.StoredToken; import us.kbase.auth2.lib.token.TokenName; @@ -895,7 +896,9 @@ public void createAgentToken() throws Exception { new UserName("foo"), UID, new DisplayName("bar"), Instant.now()) .build(); - createToken(user, new HashMap<>(), 7 * 24 * 3600 * 1000L, TokenType.AGENT); + createToken( + user, new HashMap<>(), 7 * 24 * 3600 * 1000L, TokenType.AGENT, MFAStatus.NOT_USED + ); } @Test @@ -916,7 +919,7 @@ public void createDevToken() throws Exception { .withEmailAddress(new EmailAddress("f@h.com")) .withRole(Role.DEV_TOKEN).build(); - createToken(user, new HashMap<>(), 90 * 24 * 3600 * 1000L, TokenType.DEV); + createToken(user, new HashMap<>(), 90 * 24 * 3600 * 1000L, TokenType.DEV, MFAStatus.USED); } @Test @@ -948,7 +951,13 @@ public void createServToken() throws Exception { .withEmailAddress(new EmailAddress("f@h.com")) .withRole(Role.SERV_TOKEN).build(); - createToken(user, new HashMap<>(), 100_000_000L * 24 * 3600 * 1000L, TokenType.SERV); + createToken( + user, + new HashMap<>(), + 100_000_000L * 24 * 3600 * 1000L, + TokenType.SERV, + MFAStatus.USED + ); } @Test @@ -1051,6 +1060,15 @@ private void createToken( final Map lifetimes, final long expectedLifetime, final TokenType tokenType) throws Exception { + createToken(user, lifetimes, expectedLifetime, tokenType, MFAStatus.UNKNOWN); + } + + private void createToken( + final AuthUser user, + final Map lifetimes, + final long expectedLifetime, + final TokenType tokenType, + final MFAStatus mfa) throws Exception { final TestMocks testauth = initTestMocks(); final AuthStorage storage = testauth.storageMock; final Authentication auth = testauth.auth; @@ -1064,7 +1082,7 @@ private void createToken( final Instant time = Instant.ofEpochMilli(100000); final StoredToken ht = StoredToken.getBuilder( TokenType.LOGIN, UUID.randomUUID(), user.getUserName()) - .withLifeTime(Instant.now(), Instant.now()).build(); + .withLifeTime(Instant.now(), Instant.now()).withMFA(mfa).build(); when(storage.getToken(t.getHashedToken())).thenReturn(ht, (StoredToken) null); @@ -1088,6 +1106,7 @@ private void createToken( verify(storage).storeToken(StoredToken.getBuilder(tokenType, id, user.getUserName()) .withLifeTime(time, expiration) .withTokenName(new TokenName("a name")) + .withMFA(mfa) .withContext(TokenCreationContext.getBuilder() .withNullableDevice("device").build()).build(), "p40z9I2zpElkQqSkhbW6KG3jSgMRFr3ummqjSe7OzOc="); @@ -1100,6 +1119,7 @@ private void createToken( StoredToken.getBuilder(tokenType, id, user.getUserName()) .withLifeTime(time, time.plusMillis(expectedLifetime)) .withTokenName(new TokenName("a name")) + .withMFA(mfa) .withContext(TokenCreationContext.getBuilder() .withNullableDevice("device").build()).build(), "this is a token"); diff --git a/src/test/java/us/kbase/test/auth2/providers/GlobusIdentityProviderTest.java b/src/test/java/us/kbase/test/auth2/providers/GlobusIdentityProviderTest.java index 57d951d9..0b01fa15 100644 --- a/src/test/java/us/kbase/test/auth2/providers/GlobusIdentityProviderTest.java +++ b/src/test/java/us/kbase/test/auth2/providers/GlobusIdentityProviderTest.java @@ -329,7 +329,7 @@ public void returnsBadAudience() throws Exception { .put("username", "aUsername") .put("name", "fullname") .put("email", "anEmail") - .put("identities_set", + .put("identity_set", Arrays.asList("ident1", "anID", "ident2")) .build())); failGetIdentities(idp, authCode, "pixydust", false, new IdentityRetrievalException( @@ -408,7 +408,7 @@ public void returnsBadSecondaryIdentityList() throws Exception { .put("username", "aUsername") .put("name", "fullname") .put("email", "anEmail") - .put("identities_set", + .put("identity_set", Arrays.asList("id1 ", "anID", "\nid2")) .build())); @@ -443,7 +443,7 @@ public void returnsBadResponseSecondaryID() throws Exception { .put("username", "aUsername") .put("name", "fullname") .put("email", "anEmail") - .put("identities_set", + .put("identity_set", Arrays.asList("id1 ", "anID")) .build()); @@ -543,12 +543,12 @@ private void setUpCallPrimaryID( final ParameterBody parameterBody; if (includeIdentitiesSet) { parameterBody = new ParameterBody( - new Parameter("include", "identities_set"), + new Parameter("include", "identity_set"), new Parameter("token", authtoken)); } else { parameterBody = new ParameterBody( new Parameter("token", authtoken), - new Parameter(not("include"), not("identities_set"))); + new Parameter(not("include"), not("identity_set"))); } mockClientAndServer.when( new HttpRequest() @@ -691,7 +691,7 @@ public void getIdentityWithSecondariesAndLoginURLAndEnvironment() throws Excepti .put("username", "aUsername") .put("name", "fullname") .put("email", "anEmail") - .put("identities_set", + .put("identity_set", Arrays.asList("id1 ", "anID", "\nid2")) .build())); @@ -805,7 +805,7 @@ private void getIdentityWithoutSecondariesAndLinkURL(final String env, final Str "username", "aUsername2", "name", null, "email", null, - "identities_set", Arrays.asList("anID2 \n")))); + "identity_set", Arrays.asList("anID2 \n")))); final IdentityProviderResponse ipr = idp.getIdentities( authCode, "pkcepkcepkcepkcepkcepkce", true, env); assertThat("incorrect ident set", ipr, is(IdentityProviderResponse.from( diff --git a/src/test/java/us/kbase/test/auth2/service/common/ServiceCommonTest.java b/src/test/java/us/kbase/test/auth2/service/common/ServiceCommonTest.java index ad9ccae8..482b3181 100644 --- a/src/test/java/us/kbase/test/auth2/service/common/ServiceCommonTest.java +++ b/src/test/java/us/kbase/test/auth2/service/common/ServiceCommonTest.java @@ -47,7 +47,7 @@ public class ServiceCommonTest { public static final String SERVICE_NAME = "Authentication Service"; - public static final String SERVER_VER = "0.8.0"; + public static final String SERVER_VER = "0.8.1"; public static final String GIT_ERR = "Missing git commit file gitcommit, should be in us.kbase.auth2"; diff --git a/src/test/java/us/kbase/test/auth2/service/ui/TokensTest.java b/src/test/java/us/kbase/test/auth2/service/ui/TokensTest.java index fe426485..e69569c5 100644 --- a/src/test/java/us/kbase/test/auth2/service/ui/TokensTest.java +++ b/src/test/java/us/kbase/test/auth2/service/ui/TokensTest.java @@ -428,6 +428,7 @@ public void createTokenMaximalInput() throws Exception { TokenType.LOGIN, UUID.randomUUID(), new UserName("whoo")) .withLifeTime(Instant.ofEpochMilli(10000), 1000000000000000L) + .withMFA(MFAStatus.USED) .build(), token.getHashedToken().getTokenHash()); @@ -464,11 +465,13 @@ public void createTokenMaximalInput() throws Exception { UUID.fromString(id); // ensures the id is a valid uuid TestCommon.assertCloseToNow(created); - assertThat("incorrect expires", expires, is(created + 100_000_000L * 24 * 3600 * 1000L)); + assertThat( + "incorrect expires", expires, is(created + 100_000_000L * 24 * 3600 * 1000L) + ); ServiceTestUtils.checkStoredToken(manager, newtoken, id, created, ImmutableMap.of("foo", "bar", "baz", "bat"), - new UserName("whoo"), TokenType.SERV, MFAStatus.UNKNOWN, "foo", + new UserName("whoo"), TokenType.SERV, MFAStatus.USED, "foo", 100_000_000L * 24 * 3600 * 1000L); @@ -487,7 +490,7 @@ public void createTokenMaximalInput() throws Exception { ServiceTestUtils.checkReturnedToken(manager, json, ImmutableMap.of("foo", "bar", "baz", "bat"), - new UserName("whoo"), TokenType.SERV, MFAStatus.UNKNOWN, "foo", + new UserName("whoo"), TokenType.SERV, MFAStatus.USED, "foo", 100_000_000L * 24 * 3600 * 1000L, true); } }