Skip to content

Commit

Permalink
Implement configurable expiry of api tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
tumbl3w33d committed May 2, 2024
1 parent 6c6cd93 commit 352d8e7
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
@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) {
@Named("${nexus.tasks.oauth2-proxy.api-token-invalidate.cron:-0 0 0 * * ?}") final String taskCron) {
this.taskScheduler = checkNotNull(taskScheduler);
this.taskCron = checkNotNull(taskCron);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
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 com.github.tumbl3w33d.OAuth2ProxyRealm.generateSecureRandomString;
import static org.sonatype.nexus.logging.task.TaskLogType.NEXUS_LOG_ONLY;

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;

Expand All @@ -20,6 +21,8 @@
import org.sonatype.nexus.orient.DatabaseInstance;
import org.sonatype.nexus.scheduling.Cancelable;
import org.sonatype.nexus.scheduling.TaskSupport;
import org.sonatype.nexus.security.user.UserManager;
import org.sonatype.nexus.security.user.UserNotFoundException;

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;
Expand All @@ -34,14 +37,30 @@ public class OAuth2ProxyApiTokenInvalidateTask
private final Logger logger = LoggerFactory.getLogger(OAuth2ProxyApiTokenInvalidateTask.class.getName());

private final DatabaseInstance databaseInstance;
private final UserManager nexusAuthenticatingRealm;

@Inject
public OAuth2ProxyApiTokenInvalidateTask(@Named(OAuth2ProxyDatabase.NAME) DatabaseInstance databaseInstance) {
public OAuth2ProxyApiTokenInvalidateTask(@Named(OAuth2ProxyDatabase.NAME) DatabaseInstance databaseInstance,
final List<UserManager> userManagers) {

this.databaseInstance = databaseInstance;

this.nexusAuthenticatingRealm = userManagers.stream()
.filter(um -> um.getAuthenticationRealmName() == "NexusAuthenticatingRealm")
.findFirst().get();

OAuth2ProxyRealm.ensureUserLoginTimestampSchema(databaseInstance, log);
}

private void resetPassword(String userId) {
try {
nexusAuthenticatingRealm.changePassword(userId, generateSecureRandomString(32));
logger.debug("Password reset for user {} succeeded", userId);
} catch (UserNotFoundException e) {
logger.error("Unable to reset password of user {} - {}", userId, e);
}
}

@Override
protected Void execute() throws Exception {
try (ODatabaseDocumentTx db = databaseInstance.acquire()) {
Expand All @@ -54,18 +73,29 @@ protected Void execute() throws Exception {
logger.debug("Nothing to do");
} else {
for (ODocument userLogin : userLogins) {
String userId = userLogin.field(FIELD_USER_ID);
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),

Instant lastLoginInstant = lastLoginDate.toInstant();
Instant nowInstant = Instant.now();

logger.debug("Last known login for {} was {}", userId,
formatDateString(lastLoginDate));
// if > 30d passed, reset user password
/*
* if (!lastLoginLocalDate.equals(nowLocalDate)) {
* // TODO
* }
*/

long timePassed = ChronoUnit.DAYS.between(lastLoginInstant, nowInstant);

int configuredDuration = getConfiguration()
.getInteger(OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_EXPIRY, 1);

logger.debug("Time passed since login: {} - configured maximum: {}", timePassed,
configuredDuration);

if (timePassed >= configuredDuration) {
resetPassword(userId);
logger.info(
"Reset api token of user {} because they did not login via OAuth2 Proxy for a while",
userId);
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@
import javax.inject.Named;
import javax.inject.Singleton;

import org.sonatype.nexus.formfields.FormField;
import org.sonatype.nexus.formfields.NumberTextFormField;
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);
}
extends TaskDescriptorSupport {

public static final String TYPE_ID = "oauth2-proxy-api-token.cleanup";

public static final String CONFIG_EXPIRY = TYPE_ID + "-expiry";

private static final NumberTextFormField field = new NumberTextFormField(CONFIG_EXPIRY,
"Expiration in days",
"After this duration the API token will be overwritten and the user must renew it interactively.",
FormField.MANDATORY).withMinimumValue(1).withInitialValue(30);

@Inject
public OAuth2ProxyApiTokenInvalidateTaskDescriptor() {
super(TYPE_ID, OAuth2ProxyApiTokenInvalidateTask.class, "OAuth2 Proxy API token invalidator",
TaskDescriptorSupport.VISIBLE, TaskDescriptorSupport.EXPOSED,
TaskDescriptorSupport.REQUEST_RECOVERY, new FormField[] { field });
}

@Override
public boolean allowConcurrentRun() {
return false;
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/github/tumbl3w33d/OAuth2ProxyRealm.java
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ public static void ensureUserLoginTimestampSchema(DatabaseInstance dbInstance, L
oAuth2ProxyUserLogin.createProperty(FIELD_LAST_LOGIN, OType.DATETIME)
.setNotNull(true);
}
logger.info("created schema class for " + CLASS_USER_LOGIN);
logger.info("Ensured schema class exists for " + CLASS_USER_LOGIN);
} catch (Exception e) {
logger.error("Failed to ensure schema for " + CLASS_USER_LOGIN);
}
Expand Down

0 comments on commit 352d8e7

Please sign in to comment.