Skip to content

Commit

Permalink
feat: Support Super Gluu one step authentication to Fido2 server #3593 (
Browse files Browse the repository at this point in the history
#3599)

* feat: Support Super Gluu one step authentication to Fido2 server #3593

* feat: add sample request/response for one/two steps

* feat: enrollment proxy for Super Gluu

* chore: allow to process Super Gluu auth request

* feat: add super gluu authentication flow support

* feat: update to conform Jans

* feat: update SG script and services to conform Fido2 server

* feat: add fido2 device registration services to jans-auth-server

* feat: full 2 step Super Gluu support

* feat: user filter to search user's devices for specifc domain

* fix: super_gluu_script

* fix: super Gluu script

* feat: support one_step Super Gluu enrollment

* feat: clean up jans-auth-server static config

* Revert "fix: super_gluu_script"

This reverts commit f0e1713.

* Revert "fix: super Gluu script"

This reverts commit 20512c4.

* feat: super Gluu uses applicationId isntead of applicationId domain

* feat: support Super Gluu one_step authentication

* feat: add separate base DN for one step auth requests

* feat: add super Fluu config option and disable it's API by default

* feat: fixes in two step flow to conform katest API

* feat move generic attributes to base bean

* feat: remove unused services

* chore: review script

* chore: code review

* chore: fix formatting

* feat: add missing base fido2 branch

* chore: code review

* chore: review validators

* feat: move Super Gluu adaptors code to separate services

* chore: optimizations

* chore: remove unused methods

* feat: remove U2F clean up jobs

* feat: more input parameters validations

* feat: final optimizations and fixes

Co-authored-by: Madhumita <madhu@gluu.org>
  • Loading branch information
yurem and maduvena committed Jan 27, 2023
1 parent 4f2236d commit c013b16
Show file tree
Hide file tree
Showing 58 changed files with 2,434 additions and 454 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
*/
public abstract class AttributeService extends io.jans.service.AttributeService {

/**
*
*/
private static final long serialVersionUID = -990409035168814270L;

@Inject
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Janssen Project software is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2020, Janssen Project
*/

package io.jans.as.common.service.common.fido2;

import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;

import org.slf4j.Logger;

import io.jans.as.common.model.common.User;
import io.jans.as.common.service.common.UserService;
import io.jans.as.model.config.StaticConfiguration;
import io.jans.orm.PersistenceEntryManager;
import io.jans.orm.model.base.SimpleBranch;
import io.jans.orm.model.fido2.Fido2RegistrationData;
import io.jans.orm.model.fido2.Fido2RegistrationEntry;
import io.jans.orm.model.fido2.Fido2RegistrationStatus;
import io.jans.orm.search.filter.Filter;
import io.jans.util.StringHelper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

/**
* Every registration is persisted under Person Entry
* @author Yuriy Movchan
* @version May 08, 2020
*/
@ApplicationScoped
public class RegistrationPersistenceService {

@Inject
private Logger log;

@Inject
private PersistenceEntryManager persistenceEntryManager;

@Inject
private UserService userService;

@Inject
private StaticConfiguration staticConfiguration;

public void save(Fido2RegistrationEntry registrationEntry) {
prepareBranch(registrationEntry.getUserInum());

persistenceEntryManager.persist(registrationEntry);
}

public void update(Fido2RegistrationEntry registrationEntry) {
prepareBranch(registrationEntry.getUserInum());

Date now = new GregorianCalendar(TimeZone.getTimeZone("UTC")).getTime();

Fido2RegistrationData registrationData = registrationEntry.getRegistrationData();
registrationData.setUpdatedDate(now);
registrationData.setUpdatedBy(registrationData.getUsername());

registrationEntry.setRegistrationStatus(registrationData.getStatus());

persistenceEntryManager.merge(registrationEntry);
}

public void addBranch(final String baseDn) {
SimpleBranch branch = new SimpleBranch();
branch.setOrganizationalUnitName("fido2_register");
branch.setDn(baseDn);

persistenceEntryManager.persist(branch);
}

public boolean containsBranch(final String baseDn) {
return persistenceEntryManager.contains(baseDn, SimpleBranch.class);
}

public String prepareBranch(final String userInum) {
String baseDn = getBaseDnForFido2RegistrationEntries(userInum);
if (!persistenceEntryManager.hasBranchesSupport(baseDn)) {
return baseDn;
}

// Create Fido2 base branch for registration entries if needed
if (!containsBranch(baseDn)) {
addBranch(baseDn);
}

return baseDn;
}

public Fido2RegistrationEntry findRegisteredUserDevice(String userInum, String deviceId, String... returnAttributes) {
String baseDn = getBaseDnForFido2RegistrationEntries(userInum);
if (persistenceEntryManager.hasBranchesSupport(baseDn)) {
if (!containsBranch(baseDn)) {
return null;
}
}

String deviceDn = getDnForRegistrationEntry(userInum, deviceId);

return persistenceEntryManager.find(deviceDn, Fido2RegistrationEntry.class, returnAttributes);
}

public List<Fido2RegistrationEntry> findByRpRegisteredUserDevices(String userName, String rpId, String ... returnAttributes) {
String userInum = userService.getUserInum(userName);
if (userInum == null) {
return Collections.emptyList();
}

String baseDn = getBaseDnForFido2RegistrationEntries(userInum);
if (persistenceEntryManager.hasBranchesSupport(baseDn)) {
if (!containsBranch(baseDn)) {
return Collections.emptyList();
}
}

Filter userInumFilter = Filter.createEqualityFilter("personInum", userInum);
Filter registeredFilter = Filter.createEqualityFilter("jansStatus", Fido2RegistrationStatus.registered.getValue());
Filter appIdFilter = Filter.createEqualityFilter("jansApp", rpId);
Filter filter = Filter.createANDFilter(userInumFilter, registeredFilter, appIdFilter);

List<Fido2RegistrationEntry> fido2RegistrationnEntries = persistenceEntryManager.findEntries(baseDn, Fido2RegistrationEntry.class, filter, returnAttributes);

return fido2RegistrationnEntries;
}


public boolean attachDeviceRegistrationToUser(String userInum, String deviceDn) {
Fido2RegistrationEntry registrationEntry = persistenceEntryManager.find(Fido2RegistrationEntry.class, deviceDn);
if (registrationEntry == null) {
return false;
}

User user = userService.getUserByInum(userInum, "uid");
if (user == null) {
return false;
}

persistenceEntryManager.remove(deviceDn, Fido2RegistrationEntry.class);

final String id = UUID.randomUUID().toString();

String userAttestationDn = getDnForRegistrationEntry(userInum, id);
registrationEntry.setId(id);
registrationEntry.setDn(userAttestationDn);
registrationEntry.setUserInum(userInum);

Fido2RegistrationData registrationData = registrationEntry.getRegistrationData();
registrationData.setUsername(user.getUserId());
registrationEntry.clearExpiration();

save(registrationEntry);

return true;
}

public Fido2RegistrationEntry findOneStepUserDeviceRegistration(String deviceDn) {
Fido2RegistrationEntry registrationEntry = persistenceEntryManager.find(Fido2RegistrationEntry.class, deviceDn);

return registrationEntry;
}

public String getDnForRegistrationEntry(String userInum, String jsId) {
// Build DN string for Fido2 registration entry
String baseDn = getBaseDnForFido2RegistrationEntries(userInum);
if (StringHelper.isEmpty(jsId)) {
return baseDn;
}
return String.format("jansId=%s,%s", jsId, baseDn);
}

public String getBaseDnForFido2RegistrationEntries(String userInum) {
final String userBaseDn = getDnForUser(userInum); // "ou=fido2_register,inum=1234,ou=people,o=jans"
if (StringHelper.isEmpty(userInum)) {
return userBaseDn;
}

return String.format("ou=fido2_register,%s", userBaseDn);
}

public String getDnForUser(String userInum) {
String peopleDn = getBasedPeopleDn();
if (StringHelper.isEmpty(userInum)) {
return peopleDn;
}

return String.format("inum=%s,%s", userInum, peopleDn);
}

public String getBasedPeopleDn() {
return staticConfiguration.getBaseDn().getPeople();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public class BaseDnConfiguration {
private String par;
@XmlElement(name = "ssa")
private String ssa;
@XmlElement(name = "fido2Attestation")
private String fido2Attestation;
@XmlElement(name = "fido2Assertion")
private String fido2Assertion;

public String getStat() {
return stat;
Expand Down Expand Up @@ -213,4 +217,21 @@ public String getSsa() {
public void setSsa(String ssa) {
this.ssa = ssa;
}

public String getFido2Attestation() {
return fido2Attestation;
}

public void setFido2Attestation(String fido2Attestation) {
this.fido2Attestation = fido2Attestation;
}

public String getFido2Assertion() {
return fido2Assertion;
}

public void setFido2Assertion(String fido2Assertion) {
this.fido2Assertion = fido2Assertion;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,12 @@ private static String createProcessedKey(Map.Entry<String, Class<?>> baseDn) {
}

private Map<String, Class<?>> createCleanServiceBaseDns() {
final String u2fBase = staticConfiguration.getBaseDn().getU2fBase();

final Map<String, Class<?>> cleanServiceBaseDns = Maps.newHashMap();

cleanServiceBaseDns.put(staticConfiguration.getBaseDn().getClients(), Client.class);
cleanServiceBaseDns.put(umaPctService.branchBaseDn(), UmaPCT.class);
cleanServiceBaseDns.put(umaResourceService.getBaseDnForResource(), UmaResource.class);
cleanServiceBaseDns.put(String.format("ou=registration_requests,%s", u2fBase), RegisterRequestMessageLdap.class);
cleanServiceBaseDns.put(String.format("ou=registered_devices,%s", u2fBase), DeviceRegistration.class);
cleanServiceBaseDns.put(metricService.buildDn(null, null, ApplicationType.OX_AUTH), MetricEntry.class);
cleanServiceBaseDns.put(staticConfiguration.getBaseDn().getTokens(), TokenEntity.class);
cleanServiceBaseDns.put(staticConfiguration.getBaseDn().getAuthorizations(), ClientAuthorization.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
@Named
public class SessionIdService {

public static final String SESSION_CUSTOM_STATE = "session_custom_state";
private static final int MAX_MERGE_ATTEMPTS = 3;
private static final int DEFAULT_LOCAL_CACHE_EXPIRATION = 2;

Expand Down Expand Up @@ -335,13 +334,21 @@ public SessionId getSessionId() {
sessionId = identity.getSessionId().getId();
}

SessionId result = null;
if (StringHelper.isNotEmpty(sessionId)) {
return getSessionId(sessionId);
result = getSessionId(sessionId);
if ((result == null) && identity.getSessionId() != null) {
// Here we cover scenario when user were redirected from /device-code to ACR method
// which call this method in prepareForStep for step 1. The cookie in this case is not updated yet.
// hence actual information about session_id only in identity.
sessionId = identity.getSessionId().getId();
result = getSessionId(sessionId);
}
} else {
log.trace("Session cookie not exists");
}

return null;
return result;
}

public Map<String, String> getSessionAttributes(SessionId sessionId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.fasterxml.jackson.annotation.JsonProperty;
import io.jans.as.model.common.FeatureFlagType;
import io.jans.as.model.config.Constants;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.common.model.session.SessionId;
import io.jans.as.server.service.CookieService;
Expand Down Expand Up @@ -36,6 +37,8 @@
@Path("/")
public class CheckSessionStatusRestWebServiceImpl {

public static final String SESSION_CUSTOM_STATE = "session_custom_state";

@Inject
private Logger log;

Expand Down Expand Up @@ -64,7 +67,7 @@ public Response requestCheckSessionStatus(@Context HttpServletRequest httpReques
response.setState(sessionId.getState().getValue());
response.setAuthTime(sessionId.getAuthenticationTime());

String sessionCustomState = sessionId.getSessionAttributes().get(SessionIdService.SESSION_CUSTOM_STATE);
String sessionCustomState = sessionId.getSessionAttributes().get(SESSION_CUSTOM_STATE);
if (StringHelper.isNotEmpty(sessionCustomState)) {
response.setCustomState(sessionCustomState);
}
Expand All @@ -73,7 +76,9 @@ public Response requestCheckSessionStatus(@Context HttpServletRequest httpReques
String responseJson = ServerUtil.asJson(response);
log.debug("Check session status response: '{}'", responseJson);

return Response.ok().type(MediaType.APPLICATION_JSON).entity(responseJson).build();
return Response.ok().type(MediaType.APPLICATION_JSON).entity(responseJson)
.cacheControl(ServerUtil.cacheControlWithNoStoreTransformAndPrivate())
.header(Constants.PRAGMA, Constants.NO_CACHE).build();
}

class CheckSessionResponse {
Expand Down
2 changes: 1 addition & 1 deletion jans-auth-server/server/src/main/webapp/js/gluu-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ var gluu_auth = {

(function worker() {
$.ajax({
url: '/oxauth/restv1/session_status',
url: '/jans-auth/restv1/session_status',
cache: false,
timeout: gluu_auth.checker.timeout,
success: function(result, status, xhr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

public enum AttestationFormat {

fido_u2f("fido-u2f"), packed("packed"), tpm("tpm"), android_key("android-key"), android_safetynet("android-safetynet"), none("none"), apple("apple");
fido_u2f("fido-u2f"), packed("packed"), tpm("tpm"), android_key("android-key"), android_safetynet("android-safetynet"), none("none"), apple("apple"),
fido_u2f_super_gluu("fido-u2f-super-gluu");

private final String fmt;

Expand Down
Loading

0 comments on commit c013b16

Please sign in to comment.