summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorValerij Fredriksen <valerij92@gmail.com>2021-01-20 15:35:54 +0100
committerValerij Fredriksen <valerij92@gmail.com>2021-01-20 16:23:58 +0100
commitfcac16adb0c995b862afb30998b4c3e86452df0d (patch)
treefe6aaadf323628d21c41b275f7d1bc4cf2ee1f48 /controller-server
parent7fce699b7816b71fc4f349e20ec699dbb4266939 (diff)
Create a last login filter
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java73
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java59
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