Skip to content

Commit

Permalink
Apikey authenticate (#1569)
Browse files Browse the repository at this point in the history
* API key authenticate

* refactor scope

* add feature flag and scope can be null

* remove extra dependency

* add accessKey in oAuthSession

* fix unit tests

* rename variable
  • Loading branch information
maggarwal13 authored Oct 25, 2024
1 parent 0caa7fc commit 9d14ab8
Show file tree
Hide file tree
Showing 20 changed files with 266 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.ripe.db.whois.api.rest;

import jakarta.servlet.http.HttpServletRequest;
import net.ripe.db.whois.common.apiKey.OAuthSession;

public class BearerTokenExtractor {

public static OAuthSession extract(final HttpServletRequest request) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import net.ripe.db.whois.common.rpsl.ObjectType;
import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.common.sso.AuthServiceClient;
import net.ripe.db.whois.update.domain.APIKeyCredential;
import net.ripe.db.whois.update.domain.ClientCertificateCredential;
import net.ripe.db.whois.update.domain.Credential;
import net.ripe.db.whois.update.domain.Credentials;
Expand Down Expand Up @@ -208,6 +209,10 @@ private Credentials createCredentials(final UpdateContext updateContext, final L
}
}

if (updateContext.getOAuthSession() != null) {
credentials.add(APIKeyCredential.createOfferedCredential(updateContext.getOAuthSession()));
}

return new Credentials(credentials);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.common.sso.AuthServiceClientException;
import net.ripe.db.whois.common.sso.SsoTokenTranslator;
import net.ripe.db.whois.update.domain.APIKeyCredential;
import net.ripe.db.whois.update.domain.Action;
import net.ripe.db.whois.update.domain.ClientCertificateCredential;
import net.ripe.db.whois.update.domain.Credential;
Expand Down Expand Up @@ -80,6 +81,7 @@ public UpdateContext initContext(final Origin origin, final String ssoToken, fin
final UpdateContext updateContext = new UpdateContext(loggerContext);
setSsoSessionToContext(updateContext, ssoToken);
setClientCertificates(updateContext, request);
setOAuthSession(updateContext, request);
return updateContext;
}

Expand Down Expand Up @@ -211,6 +213,10 @@ private static Paragraph createParagraph(final UpdateContext updateContext, fina
}
}

if (updateContext.getOAuthSession() != null) {
credentials.add(APIKeyCredential.createOfferedCredential(updateContext.getOAuthSession()));
}

return new Paragraph(rpslObject.toString(), new Credentials(credentials));
}

Expand All @@ -233,6 +239,10 @@ public void setSsoSessionToContext(final UpdateContext updateContext, final Stri
}
}

private void setOAuthSession(final UpdateContext updateContext, final HttpServletRequest request) {
updateContext.setOAuthSession(BearerTokenExtractor.extract(request));
}

public void setClientCertificates(final UpdateContext updateContext, final HttpServletRequest request) {
updateContext.setClientCertificates(ClientCertificateExtractor.getClientCertificates(request));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.ripe.db.whois.common.apiKey;

import com.google.common.base.MoreObjects;

import java.time.LocalDateTime;
import java.util.List;

public class OAuthSession {

private final String application;

private final String email;

private final String accessKey;

private final String uuid;

private final LocalDateTime expirationDate;

private final List<String> scopes;

public OAuthSession(final String application, final String accessKey, final String email, final String uuid, final LocalDateTime expirationDate, final List<String> scopes) {
this.application = application;
this.email = email;
this.uuid = uuid;
this.expirationDate = expirationDate;
this.scopes = scopes;
this.accessKey = accessKey;
}

public String getApplication() {
return application;
}

public String getEmail() {
return email;
}

public String getUuid() {
return uuid;
}

public LocalDateTime getExpirationDate() {
return expirationDate;
}

public String getAccessKey() {
return accessKey;
}

public List<String> getScopes() {
return scopes;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("application", application)
.add("accessKey", accessKey)
.add("email", email)
.add("uuid", uuid)
.add("expirationDate", expirationDate.toString())
.add("scopes", scopes)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package net.ripe.db.whois.update.authentication.credential;

import net.ripe.db.whois.common.apiKey.OAuthSession;
import net.ripe.db.whois.common.rpsl.ObjectType;
import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.update.domain.APIKeyCredential;
import net.ripe.db.whois.update.domain.PreparedUpdate;
import net.ripe.db.whois.update.domain.SsoCredential;
import net.ripe.db.whois.update.domain.Update;
import net.ripe.db.whois.update.domain.UpdateContext;
import net.ripe.db.whois.update.log.LoggerContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Component
public class ApiKeyCredentialValidator implements CredentialValidator<APIKeyCredential, SsoCredential> {
private final LoggerContext loggerContext;

@Value("${apikey.authenticate.enabled:false}")
private boolean enabled;

@Autowired
public ApiKeyCredentialValidator(final LoggerContext loggerContext) {
this.loggerContext = loggerContext;
}

@Override
public Class<SsoCredential> getSupportedCredentials() {
return SsoCredential.class;
}

@Override
public Class<APIKeyCredential> getSupportedOfferedCredentialType() {
return APIKeyCredential.class;
}

@Override
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<APIKeyCredential> offeredCredentials, final SsoCredential knownCredential, final RpslObject maintainer) {
if(!enabled) {
return false;
}

for (final APIKeyCredential offered : offeredCredentials) {

final OAuthSession oAuthSession = offered.getOfferedOAuthSession();

if(!oAuthSession.getScopes().isEmpty()) {
final ScopeFormatter scopeFormatter = new ScopeFormatter(offered.getOfferedOAuthSession().getScopes().getFirst());
if(!validateScope(maintainer, scopeFormatter)) {
continue;
}
}

if (oAuthSession.getUuid().equals(knownCredential.getKnownUuid())) {
log(update, String.format("Validated %s with API KEY for user: %s with apiKey: %s.", update.getFormattedKey(), oAuthSession.getEmail(), oAuthSession.getAccessKey()));

update.getUpdate().setEffectiveCredential(oAuthSession.getAccessKey(), Update.EffectiveCredentialType.APIKEY);
return true;
}
}
return false;
}

private void log(final PreparedUpdate update, final String message) {
loggerContext.logString(update.getUpdate(), getClass().getCanonicalName(), message);
}

private static boolean validateScope(final RpslObject maintainer, final ScopeFormatter scopeFormatter) {
return scopeFormatter.getAppName().equalsIgnoreCase("whois")
&& scopeFormatter.getScopeType().equalsIgnoreCase(ObjectType.MNTNER.getName())
&& scopeFormatter.getScopeKey().equalsIgnoreCase(maintainer.getKey().toString());
}

static class ScopeFormatter {

final String appName;
final String scopeType;
final String scopeKey;

public ScopeFormatter(final String scope) {
final String[] parts = scope.split(":|\\.");
this.appName = parts[0];
this.scopeType = parts[1];
this.scopeKey = parts[2];
}

public String getScopeType() {
return scopeType;
}

public String getScopeKey() {
return scopeKey;
}

public String getAppName() {
return appName;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private boolean hasValidCredentialForCandidate(final PreparedUpdate update, fina

final Class<? extends Credential> credentialClass = credential.getClass();
for (CredentialValidator credentialValidator : credentialValidatorMap.get(credentialClass)) {
if (credentialValidator.hasValidCredential(update, updateContext, offered.ofType(credentialValidator.getSupportedOfferedCredentialType()), credential)) {
if (credentialValidator.hasValidCredential(update, updateContext, offered.ofType(credentialValidator.getSupportedOfferedCredentialType()), credential, maintainer)) {
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public Class<ClientCertificateCredential> getSupportedOfferedCredentialType() {
}

@Override
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<ClientCertificateCredential> offeredCredentials, final X509Credential knownCredential) {
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<ClientCertificateCredential> offeredCredentials, final X509Credential knownCredential, final RpslObject maintainer) {
if (!enabled) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.ripe.db.whois.update.authentication.credential;

import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.update.domain.Credential;
import net.ripe.db.whois.update.domain.PreparedUpdate;
import net.ripe.db.whois.update.domain.UpdateContext;
Expand All @@ -12,5 +13,5 @@ interface CredentialValidator<T extends Credential, K extends Credential> {

Class<T> getSupportedOfferedCredentialType();

boolean hasValidCredential(PreparedUpdate update, UpdateContext updateContext, Collection<T> offeredCredentials, K knownCredential);
boolean hasValidCredential(PreparedUpdate update, UpdateContext updateContext, Collection<T> offeredCredentials, K knownCredential, RpslObject maintainer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import net.ripe.db.whois.common.Message;
import net.ripe.db.whois.common.Messages;
import net.ripe.db.whois.common.rpsl.PasswordHelper;
import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.update.domain.PasswordCredential;
import net.ripe.db.whois.update.domain.PreparedUpdate;
import net.ripe.db.whois.update.domain.Update;
Expand Down Expand Up @@ -36,7 +37,8 @@ public Class<PasswordCredential> getSupportedOfferedCredentialType() {
public boolean hasValidCredential(final PreparedUpdate update,
final UpdateContext updateContext,
final Collection<PasswordCredential> offeredCredentials,
final PasswordCredential knownCredential) {
final PasswordCredential knownCredential,
final RpslObject maintainer) {

for (final PasswordCredential offeredCredential : offeredCredentials) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public Class<PgpCredential> getSupportedOfferedCredentialType() {
}

@Override
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<PgpCredential> offeredCredentials, final PgpCredential knownCredential) {
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<PgpCredential> offeredCredentials, final PgpCredential knownCredential, final RpslObject maintainer) {
for (final PgpCredential offeredCredential : offeredCredentials) {
if (verifySignedMessage(update, updateContext, offeredCredential, knownCredential)) {
update.getUpdate().setEffectiveCredential(knownCredential.getKeyId(), Update.EffectiveCredentialType.PGP);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.ripe.db.whois.update.authentication.credential;

import net.ripe.db.whois.common.rpsl.RpslObject;
import net.ripe.db.whois.update.domain.PreparedUpdate;
import net.ripe.db.whois.update.domain.SsoCredential;
import net.ripe.db.whois.update.domain.Update;
Expand Down Expand Up @@ -30,7 +31,7 @@ public Class<SsoCredential> getSupportedOfferedCredentialType() {
}

@Override
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<SsoCredential> offeredCredentials, final SsoCredential knownCredential) {
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<SsoCredential> offeredCredentials, final SsoCredential knownCredential, final RpslObject maintainer) {
for (SsoCredential offered : offeredCredentials) {
if (offered.getOfferedUserSession().getUuid().equals(knownCredential.getKnownUuid())) {
log(update, String.format("Validated %s with RIPE NCC Access for user: %s.", update.getFormattedKey(), offered.getOfferedUserSession().getUsername()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public Class<X509Credential> getSupportedOfferedCredentialType() {
}

@Override
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<X509Credential> offeredCredentials, final X509Credential knownCredential) {
public boolean hasValidCredential(final PreparedUpdate update, final UpdateContext updateContext, final Collection<X509Credential> offeredCredentials, final X509Credential knownCredential, final RpslObject maintainer) {
for (final X509Credential offeredCredential : offeredCredentials) {
if (verifySignedMessage(update, updateContext, offeredCredential, knownCredential)) {
log(update, String.format("Successfully validated with keycert: %s", knownCredential.getKeyId()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.ripe.db.whois.update.domain;

import com.google.common.base.Splitter;
import net.ripe.db.whois.common.apiKey.OAuthSession;
import net.ripe.db.whois.common.sso.UserSession;

public class APIKeyCredential implements Credential {

private final OAuthSession offeredOAuthSession;

private APIKeyCredential(final OAuthSession offeredOAuthSession) {
this.offeredOAuthSession = offeredOAuthSession;
}

public static Credential createOfferedCredential(final OAuthSession offeredOAuthSession) {
return new APIKeyCredential(offeredOAuthSession);
}

public OAuthSession getOfferedOAuthSession() {
return offeredOAuthSession;
}
@Override
public String toString() {
return String.format("APIKeyCredential{offeredUserSession=%s}", offeredOAuthSession);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public EffectiveCredentialType getEffectiveCredentialType() {
}

public enum EffectiveCredentialType {
SSO, PGP, PASSWORD, X509
SSO, PGP, PASSWORD, X509, APIKEY
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.google.common.collect.Sets;
import net.ripe.db.whois.common.Message;
import net.ripe.db.whois.common.Messages;
import net.ripe.db.whois.common.apiKey.OAuthSession;
import net.ripe.db.whois.common.dao.RpslObjectUpdateInfo;
import net.ripe.db.whois.common.domain.CIString;
import net.ripe.db.whois.common.rpsl.ObjectMessages;
Expand Down Expand Up @@ -37,9 +38,11 @@ public class UpdateContext {
private final Map<String, String> ssoTranslation = Maps.newHashMap();
private final LoggerContext loggerContext;
private UserSession userSession;
private OAuthSession oAuthSession;
private List<X509CertificateWrapper> clientCertificates;
private int nrSinceRestart;
private boolean dryRun;

private boolean batchUpdate;

public UpdateContext(final LoggerContext loggerContext) {
Expand Down Expand Up @@ -322,10 +325,18 @@ public void setUserSession(final UserSession userSession) {
this.userSession = userSession;
}

public void setOAuthSession(final OAuthSession oAuthSession) {
this.oAuthSession = oAuthSession;
}

public UserSession getUserSession() {
return userSession;
}

public OAuthSession getOAuthSession() {
return oAuthSession;
}

public void setClientCertificates(final List<X509CertificateWrapper> certificates) {
this.clientCertificates = certificates;
}
Expand Down
Loading

0 comments on commit 9d14ab8

Please sign in to comment.