Skip to content

Commit

Permalink
Add task for invalidating api tokens
Browse files Browse the repository at this point in the history
It does not do that yet. Currently it only reports last login of all known oauth2 proxy users, but the persistence and scheduling is there.
  • Loading branch information
tumbl3w33d committed Apr 30, 2024
1 parent 9d38a6e commit 6c6cd93
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 10 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@
<version>${nexus.version}</version>
</dependency>

<dependency>
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-scheduling</artifactId>
<version>${nexus.version}</version>
</dependency>

<!-- UI plugin part -->
<dependency>
<groupId>org.sonatype.nexus.buildsupport</groupId>
Expand Down
6 changes: 6 additions & 0 deletions pom_for_bundle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-scheduling</artifactId>
<scope>provided</scope>
</dependency>

<!-- UI plugin part -->
<dependency>
<groupId>org.sonatype.nexus.buildsupport</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.tumbl3w33d;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.sonatype.nexus.common.app.ManagedLifecycle.Phase.TASKS;

import java.util.Date;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.sonatype.nexus.common.app.ManagedLifecycle;
import org.sonatype.nexus.common.stateguard.StateGuardLifecycleSupport;
import org.sonatype.nexus.scheduling.TaskConfiguration;
import org.sonatype.nexus.scheduling.TaskScheduler;
import org.sonatype.nexus.scheduling.schedule.Schedule;

@Named
@ManagedLifecycle(phase = TASKS)
@Singleton
public class OAuth2ProxyApiTokenInvalidateQuartz extends StateGuardLifecycleSupport {
private final TaskScheduler taskScheduler;

private final String taskCron;

@Inject
public OAuth2ProxyApiTokenInvalidateQuartz(final TaskScheduler taskScheduler,
@Named("${nexus.tasks.oauth2-proxy.api-token-invalidate.cron:-0 */5 * * * ?}") final String taskCron) {
this.taskScheduler = checkNotNull(taskScheduler);
this.taskCron = checkNotNull(taskCron);
}

@Override
protected void doStart() throws Exception {
if (!taskScheduler.listsTasks().stream()
.anyMatch((info) -> OAuth2ProxyApiTokenInvalidateTaskDescriptor.TYPE_ID
.equals(info.getConfiguration().getTypeId()))) {
TaskConfiguration configuration = taskScheduler.createTaskConfigurationInstance(
OAuth2ProxyApiTokenInvalidateTaskDescriptor.TYPE_ID);
Schedule schedule = taskScheduler.getScheduleFactory().cron(new Date(), taskCron);
taskScheduler.scheduleTask(configuration, schedule);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.github.tumbl3w33d;

import static com.github.tumbl3w33d.OAuth2ProxyRealm.CLASS_USER_LOGIN;
import static com.github.tumbl3w33d.OAuth2ProxyRealm.FIELD_LAST_LOGIN;
import static com.github.tumbl3w33d.OAuth2ProxyRealm.FIELD_USER_ID;
import static com.github.tumbl3w33d.OAuth2ProxyRealm.formatDateString;
import static org.sonatype.nexus.logging.task.TaskLogType.NEXUS_LOG_ONLY;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.nexus.logging.task.TaskLogging;
import org.sonatype.nexus.orient.DatabaseInstance;
import org.sonatype.nexus.scheduling.Cancelable;
import org.sonatype.nexus.scheduling.TaskSupport;

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;

@Named
@TaskLogging(NEXUS_LOG_ONLY)
public class OAuth2ProxyApiTokenInvalidateTask
extends TaskSupport
implements Cancelable {

private final Logger logger = LoggerFactory.getLogger(OAuth2ProxyApiTokenInvalidateTask.class.getName());

private final DatabaseInstance databaseInstance;

@Inject
public OAuth2ProxyApiTokenInvalidateTask(@Named(OAuth2ProxyDatabase.NAME) DatabaseInstance databaseInstance) {
this.databaseInstance = databaseInstance;

OAuth2ProxyRealm.ensureUserLoginTimestampSchema(databaseInstance, log);
}

@Override
protected Void execute() throws Exception {
try (ODatabaseDocumentTx db = databaseInstance.acquire()) {
db.begin();

List<ODocument> userLogins = db.query(new OSQLSynchQuery<ODocument>(
"select from " + CLASS_USER_LOGIN));

if (userLogins.isEmpty()) {
logger.debug("Nothing to do");
} else {
for (ODocument userLogin : userLogins) {
Date lastLoginDate = userLogin.field(FIELD_LAST_LOGIN);
LocalDate lastLoginLocalDate = lastLoginDate.toInstant().atZone(ZoneId.systemDefault())
.toLocalDate();
LocalDate nowLocalDate = LocalDate.now();
logger.info("Last known login for {} was {}", userLogin.field(FIELD_USER_ID),
formatDateString(lastLoginDate));
// if > 30d passed, reset user password
/*
* if (!lastLoginLocalDate.equals(nowLocalDate)) {
* // TODO
* }
*/
}

}
} catch (Exception e) {
logger.error("Failed to retrieve login timestamps - {}", e);
}
return null;
}

@Override
public String getMessage() {
return "Invalidate OAuth2 Proxy API tokens of users who did not log in for a while";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.tumbl3w33d;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import org.sonatype.nexus.scheduling.TaskDescriptorSupport;

@Named
@Singleton
public class OAuth2ProxyApiTokenInvalidateTaskDescriptor
extends TaskDescriptorSupport {
public static final String TYPE_ID = "oauth2-proxy-api-token.cleanup";

@Inject
public OAuth2ProxyApiTokenInvalidateTaskDescriptor() {
super(TYPE_ID, OAuth2ProxyApiTokenInvalidateTask.class, "OAuth2 Proxy API token invalidator",
TaskDescriptorSupport.NOT_VISIBLE, TaskDescriptorSupport.NOT_EXPOSED);
}
}
22 changes: 12 additions & 10 deletions src/main/java/com/github/tumbl3w33d/OAuth2ProxyRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class OAuth2ProxyRealm extends AuthenticatingRealm {

private static final String ALLOWED_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

static final String CLASS_USER_LOGIN = "OAuth2ProxyUserLogin";
static final String FIELD_USER_ID = "userId";
static final String FIELD_LAST_LOGIN = "lastLogin";

Expand Down Expand Up @@ -85,7 +86,7 @@ public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo

});

ensureSchema();
ensureUserLoginTimestampSchema(databaseInstance, logger);
}


Expand Down Expand Up @@ -275,19 +276,20 @@ void addPrincipal(String userId, String authRealmName) {
}
}

public void ensureSchema() {
try (ODatabaseDocumentTx db = databaseInstance.acquire()) {
public static void ensureUserLoginTimestampSchema(DatabaseInstance dbInstance, Logger logger) {
try (ODatabaseDocumentTx db = dbInstance.acquire()) {

OSchema schema = db.getMetadata().getSchema();
if (!schema.existsClass("OAUTH2PROXYUSERLOGIN")) {
OClass oAuth2ProxyUserLogin = schema.createClass("OAUTH2PROXYUSERLOGIN");
final String className = CLASS_USER_LOGIN.toUpperCase();
if (!schema.existsClass(className)) {
OClass oAuth2ProxyUserLogin = schema.createClass(className);
oAuth2ProxyUserLogin.createProperty(FIELD_USER_ID, OType.STRING).setNotNull(true);
oAuth2ProxyUserLogin.createProperty(FIELD_LAST_LOGIN, OType.DATETIME)
.setNotNull(true);
}
logger.info("created schema class for OAuth2ProxyUserLogin");
logger.info("created schema class for " + CLASS_USER_LOGIN);
} catch (Exception e) {
logger.error("Failed to ensure schema for OAuth2ProxyUserLogin");
logger.error("Failed to ensure schema for " + CLASS_USER_LOGIN);
}
}

Expand All @@ -296,7 +298,7 @@ private void recordLogin(String userId) {
db.begin();

List<ODocument> userLogins = db.query(new OSQLSynchQuery<ODocument>(
"select from OAuth2ProxyUserLogin where " + FIELD_USER_ID + " = ?", 1),
"select from " + CLASS_USER_LOGIN + " where " + FIELD_USER_ID + " = ?", 1),
userId);

ODocument userLogin;
Expand All @@ -305,7 +307,7 @@ private void recordLogin(String userId) {

if (userLogins.isEmpty()) {
logger.debug("No login recorded for {} yet", userId);
userLogin = new ODocument("OAuth2ProxyUserLogin").field(FIELD_USER_ID, userId);
userLogin = new ODocument(CLASS_USER_LOGIN).field(FIELD_USER_ID, userId);
shouldUpdate = true;
} else {
userLogin = userLogins.get(0);
Expand All @@ -330,7 +332,7 @@ private void recordLogin(String userId) {
}
}

private String formatDateString(Date date) {
static String formatDateString(Date date) {
if (date != null) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
Expand Down

0 comments on commit 6c6cd93

Please sign in to comment.