diff options
author | Valerij Fredriksen <valerij92@gmail.com> | 2021-01-20 15:35:54 +0100 |
---|---|---|
committer | Valerij Fredriksen <valerij92@gmail.com> | 2021-01-20 16:23:58 +0100 |
commit | fcac16adb0c995b862afb30998b4c3e86452df0d (patch) | |
tree | fe6aaadf323628d21c41b275f7d1bc4cf2ee1f48 /controller-server | |
parent | 7fce699b7816b71fc4f349e20ec699dbb4266939 (diff) |
Create a last login filter
Diffstat (limited to 'controller-server')
3 files changed, 149 insertions, 0 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java index ffb1aae7299..4c9cf4f105f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java @@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.security.AccessControl; import com.yahoo.vespa.hosted.controller.security.Credentials; import com.yahoo.vespa.hosted.controller.security.TenantSpec; +import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo; import com.yahoo.vespa.hosted.controller.tenant.Tenant; import java.time.Duration; @@ -121,6 +122,22 @@ public class TenantController { } } + /** + * Update last login times for the given tenant at the given user levers with the given instant, but only if the + * new instant is later + */ + public void updateLastLogin(TenantName tenantName, List<LastLoginInfo.UserLevel> userLevels, Instant loggedInAt) { + try (Lock lock = lock(tenantName)) { + Tenant tenant = require(tenantName); + LastLoginInfo loginInfo = tenant.lastLoginInfo(); + for (LastLoginInfo.UserLevel userLevel : userLevels) + loginInfo = loginInfo.withLastLoginIfLater(userLevel, loggedInAt); + + if (tenant.lastLoginInfo().equals(loginInfo)) return; // no change + curator.writeTenant(LockedTenant.of(tenant, lock).with(loginInfo).get()); + } + } + /** Deletes the given tenant. */ public void delete(TenantName tenant, Credentials credentials) { try (Lock lock = lock(tenant)) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java new file mode 100644 index 00000000000..9b1ccc09499 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java @@ -0,0 +1,73 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.filter; + +import com.google.inject.Inject; +import com.yahoo.config.provision.TenantName; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import com.yahoo.jdisc.http.filter.security.base.JsonSecurityRequestFilterBase; +import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.TenantController; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; +import com.yahoo.vespa.hosted.controller.api.role.TenantRole; +import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.administrator; +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.developer; +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.user; + +/** + * A security filter protects all controller apis. + * + * @author freva + */ +public class LastLoginUpdateFilter extends JsonSecurityRequestFilterBase { + + private static final Logger log = Logger.getLogger(LastLoginUpdateFilter.class.getName()); + + private final TenantController tenantController; + + @Inject + public LastLoginUpdateFilter(Controller controller) { + this.tenantController = controller.tenants(); + } + + @Override + public Optional<ErrorResponse> filter(DiscFilterRequest request) { + try { + SecurityContext context = (SecurityContext) request.getAttribute(SecurityContext.ATTRIBUTE_NAME); + Map<TenantName, List<LastLoginInfo.UserLevel>> userLevelsByTenant = context.roles().stream() + .flatMap(LastLoginUpdateFilter::filterTenantUserLevels) + .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); + + userLevelsByTenant.forEach((tenant, userLevels) -> tenantController.updateLastLogin(tenant, userLevels, context.issuedAt())); + } catch (Exception e) { + log.log(Level.WARNING, "Exception updating last login:", e); + } + return Optional.empty(); + } + + public static Stream<Map.Entry<TenantName, LastLoginInfo.UserLevel>> filterTenantUserLevels(Role role) { + if (!(role instanceof TenantRole)) + return Stream.empty(); + + TenantRole tenantRole = (TenantRole) role; + TenantName name = tenantRole.tenant(); + switch (tenantRole.definition()) { + case athenzTenantAdmin: + return Stream.of(Map.entry(name, user), Map.entry(name, developer), Map.entry(name, administrator)); + case reader: return Stream.of(Map.entry(name, user)); + case developer: return Stream.of(Map.entry(name, developer)); + case administrator: return Stream.of(Map.entry(name, administrator)); + default: return Stream.empty(); + } + } +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java new file mode 100644 index 00000000000..df402e8c594 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java @@ -0,0 +1,59 @@ +package com.yahoo.vespa.hosted.controller.restapi.filter; + +import com.yahoo.application.container.handler.Request; +import com.yahoo.config.provision.TenantName; +import com.yahoo.container.jdisc.RequestHandlerTestDriver; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; +import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper; +import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo; +import org.junit.Test; + +import java.time.Instant; +import java.util.Set; + +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.administrator; +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.developer; +import static com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo.UserLevel.user; + +import static org.junit.Assert.assertEquals; + +public class LastLoginUpdateFilterTest { + + private static final TenantName tenant1 = TenantName.from("tenant1"); + private static final TenantName tenant2 = TenantName.from("tenant2"); + + private final ControllerTester tester = new ControllerTester(); + private final LastLoginUpdateFilter filter = new LastLoginUpdateFilter(tester.controller()); + + @Test + public void updateLastLoginTimeTest() { + tester.createTenant(tenant1.value()); + tester.createTenant(tenant2.value()); + + request(123, Role.developer(tenant1), Role.reader(tenant1), Role.athenzTenantAdmin(tenant2)); + assertLastLoginBy(tenant1, 123L, 123L, null); + assertLastLoginBy(tenant2, 123L, 123L, 123L); + + request(321, Role.administrator(tenant1), Role.reader(tenant1)); + assertLastLoginBy(tenant1, 321L, 123L, 321L); + assertLastLoginBy(tenant2, 123L, 123L, 123L); + } + + private void assertLastLoginBy(TenantName tenantName, Long lastUserLoginAt, Long lastDeveloperLoginAt, Long lastAdministratorLoginAt) { + LastLoginInfo loginInfo = tester.controller().tenants().require(tenantName).lastLoginInfo(); + assertEquals(lastUserLoginAt, loginInfo.get(user).map(Instant::toEpochMilli).orElse(null)); + assertEquals(lastDeveloperLoginAt, loginInfo.get(developer).map(Instant::toEpochMilli).orElse(null)); + assertEquals(lastAdministratorLoginAt, loginInfo.get(administrator).map(Instant::toEpochMilli).orElse(null)); + } + + private void request(long issuedAt, Role... roles) { + SecurityContext context = new SecurityContext(() -> "bob", Set.of(roles), Instant.ofEpochMilli(issuedAt)); + Request request = new Request("/", new byte[0], Request.Method.GET, context.principal()); + request.getAttributes().put(SecurityContext.ATTRIBUTE_NAME, context); + filter.filter(new ApplicationRequestToDiscFilterRequestWrapper(request)); + } +}
\ No newline at end of file |