Skip to content

Commit

Permalink
feat(jans-auth-server): added device secret generation
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriyz committed Oct 24, 2022
1 parent abaefbb commit 17a931e
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,7 @@
import java.util.List;
import java.util.Map;

import static io.jans.as.model.authorize.AuthorizeResponseParam.ACCESS_TOKEN;
import static io.jans.as.model.authorize.AuthorizeResponseParam.AUD;
import static io.jans.as.model.authorize.AuthorizeResponseParam.CODE;
import static io.jans.as.model.authorize.AuthorizeResponseParam.EXP;
import static io.jans.as.model.authorize.AuthorizeResponseParam.EXPIRES_IN;
import static io.jans.as.model.authorize.AuthorizeResponseParam.ID_TOKEN;
import static io.jans.as.model.authorize.AuthorizeResponseParam.ISS;
import static io.jans.as.model.authorize.AuthorizeResponseParam.RESPONSE;
import static io.jans.as.model.authorize.AuthorizeResponseParam.SCOPE;
import static io.jans.as.model.authorize.AuthorizeResponseParam.SESSION_ID;
import static io.jans.as.model.authorize.AuthorizeResponseParam.SID;
import static io.jans.as.model.authorize.AuthorizeResponseParam.STATE;
import static io.jans.as.model.authorize.AuthorizeResponseParam.TOKEN_TYPE;
import static io.jans.as.model.authorize.AuthorizeResponseParam.*;

/**
* Represents an authorization response received from the authorization server.
Expand All @@ -64,6 +52,7 @@ public class AuthorizationResponse extends BaseResponse {
private String state;
private String sessionId;
private String sid;
private String deviceSecret;
private Map<String, String> customParams;
private ResponseMode responseMode;

Expand Down Expand Up @@ -203,6 +192,7 @@ private void processLocation() {
}
}

@SuppressWarnings("java:S3776")
private void loadParams(Map<String, String> params) throws UnsupportedEncodingException {
if (params.containsKey(CODE)) {
code = params.get(CODE);
Expand All @@ -216,6 +206,10 @@ private void loadParams(Map<String, String> params) throws UnsupportedEncodingEx
sid = params.get(SID);
params.remove(SID);
}
if (params.containsKey(DEVICE_SECRET)) {
deviceSecret = params.get(DEVICE_SECRET);
params.remove(DEVICE_SECRET);
}
if (params.containsKey(ACCESS_TOKEN)) {
accessToken = params.get(ACCESS_TOKEN);
params.remove(ACCESS_TOKEN);
Expand Down Expand Up @@ -319,6 +313,14 @@ public void setSid(String sid) {
this.sid = sid;
}

public String getDeviceSecret() {
return deviceSecret;
}

public void setDeviceSecret(String deviceSecret) {
this.deviceSecret = deviceSecret;
}

/**
* Gets session id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import java.util.*;

/**
* @author Yuriy Zabrovarnyy
Expand Down Expand Up @@ -76,6 +74,9 @@ public class SessionId implements Deletable, Serializable {
@AttributeName(name = "jansSessAttr")
private Map<String, String> sessionAttributes;

@AttributeName(name = "deviceSecret")
private List<String> deviceSecrets;

@AttributeName(name = "exp")
private Date expirationDate;

Expand All @@ -94,6 +95,15 @@ public class SessionId implements Deletable, Serializable {
@Expiration
private int ttl;

public List<String> getDeviceSecrets() {
if (deviceSecrets == null) deviceSecrets = new ArrayList<>();
return deviceSecrets;
}

public void setDeviceSecrets(List<String> deviceSecrets) {
this.deviceSecrets = deviceSecrets;
}

public int getTtl() {
return ttl;
}
Expand Down Expand Up @@ -305,6 +315,7 @@ public String toString() {
sb.append(", permissionGrantedMap=").append(permissionGrantedMap);
sb.append(", sessionAttributes=").append(sessionAttributes);
sb.append(", persisted=").append(persisted);
sb.append(", deviceSecrets=").append(deviceSecrets);
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class AuthorizeResponseParam {
public static final String ISS = "iss";
public static final String AUD = "aud";
public static final String EXP = "exp";
public static final String DEVICE_SECRET = "device_secret";

/**
* String that represents the End-User's login state at the OP.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class ScopeConstants {

public static final String OPENID = "openid";
public static final String OFFLINE_ACCESS = "offline_access";
public static final String DEVICE_SSO = "device_sso";

private ScopeConstants() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
@Path("/")
public class AuthorizeRestWebServiceImpl implements AuthorizeRestWebService {

private static final String SUCCESSFUL_RP_REDIRECT_COUNT = "successful_rp_redirect_count";

@Inject
private Logger log;

Expand Down Expand Up @@ -502,7 +504,7 @@ private ResponseBuilder authorize(AuthzRequest authzRequest) throws AcrChangedEx
ResponseBuilder builder = RedirectUtil.getRedirectResponseBuilder(authzRequest.getRedirectUriResponse().getRedirectUri(), authzRequest.getHttpRequest());

addCustomHeaders(builder, authzRequest);
updateSessionRpRedirect(sessionUser);
updateSession(authzRequest, sessionUser);

runCiba(authzRequest.getAuthReqId(), client, authzRequest.getHttpRequest(), authzRequest.getHttpResponse());
processDeviceAuthorization(deviceAuthzUserCode, user);
Expand Down Expand Up @@ -910,11 +912,13 @@ private Response redirectTo(String pathToRedirect, AuthzRequest authzRequest, Li
return builder.build();
}

private void updateSessionRpRedirect(SessionId sessionUser) {
int rpRedirectCount = Util.parseIntSilently(sessionUser.getSessionAttributes().get("successful_rp_redirect_count"), 0);
private void updateSession(AuthzRequest authzRequest, SessionId sessionUser) {
authzRequestService.addDeviceSecretToSession(authzRequest, sessionUser);

int rpRedirectCount = Util.parseIntSilently(sessionUser.getSessionAttributes().get(SUCCESSFUL_RP_REDIRECT_COUNT), 0);
rpRedirectCount++;

sessionUser.getSessionAttributes().put("successful_rp_redirect_count", Integer.toString(rpRedirectCount));
sessionUser.getSessionAttributes().put(SUCCESSFUL_RP_REDIRECT_COUNT, Integer.toString(rpRedirectCount));
sessionIdService.updateSessionId(sessionUser);
}

Expand All @@ -925,7 +929,7 @@ private boolean unauthenticateSession(String sessionId, HttpServletRequest httpR
private boolean unauthenticateSession(String sessionId, HttpServletRequest httpRequest, boolean isPromptFromJwt) {
SessionId sessionUser = identity.getSessionId();

if (isPromptFromJwt && sessionUser != null && !sessionUser.getSessionAttributes().containsKey("successful_rp_redirect_count")) {
if (isPromptFromJwt && sessionUser != null && !sessionUser.getSessionAttributes().containsKey(SUCCESSFUL_RP_REDIRECT_COUNT)) {
return false; // skip unauthentication because there were no at least one successful rp redirect
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
import com.google.common.collect.Sets;
import io.jans.as.common.model.common.User;
import io.jans.as.common.model.registration.Client;
import io.jans.as.common.model.session.SessionId;
import io.jans.as.common.util.CommonUtils;
import io.jans.as.common.util.RedirectUri;
import io.jans.as.model.authorize.AuthorizeErrorResponseType;
import io.jans.as.model.authorize.AuthorizeResponseParam;
import io.jans.as.model.common.Prompt;
import io.jans.as.model.common.ResponseMode;
import io.jans.as.model.common.ScopeConstants;
import io.jans.as.model.config.WebKeysConfiguration;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.crypto.AbstractCryptoProvider;
Expand All @@ -34,6 +37,7 @@
import io.jans.as.server.model.authorize.IdTokenMember;
import io.jans.as.server.model.authorize.JwtAuthorizationRequest;
import io.jans.as.server.model.authorize.ScopeChecker;
import io.jans.as.server.model.token.HandleTokenFactory;
import io.jans.as.server.par.ws.rs.ParService;
import io.jans.as.server.service.*;
import io.jans.as.server.util.ServerUtil;
Expand All @@ -52,6 +56,7 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -101,6 +106,20 @@ public class AuthzRequestService {
@Inject
private RedirectionUriService redirectionUriService;

public void addDeviceSecretToSession(AuthzRequest authzRequest, SessionId sessionId) {
if (!Arrays.asList(authzRequest.getScope().split(" ")).contains(ScopeConstants.DEVICE_SSO)) {
return;
}

final String newDeviceSecret = HandleTokenFactory.generateHandleToken();

final List<String> deviceSecrets = sessionId.getDeviceSecrets();
deviceSecrets.add(newDeviceSecret);

authzRequest.getRedirectUriResponse().getRedirectUri().addResponseParameter(AuthorizeResponseParam.DEVICE_SECRET, newDeviceSecret);
}


public boolean processPar(AuthzRequest authzRequest) {
boolean isPar = Util.isPar(authzRequest.getRequestUri());
if (!isPar && isTrue(appConfiguration.getRequirePar())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,19 @@ public SessionId getSessionBySid(@Nullable String sid) {
return entries.get(0);
}

@Nullable
public SessionId getSessionByDeviceSecret(@Nullable String deviceSecret) {
if (StringUtils.isBlank(deviceSecret)) {
return null;
}

final List<SessionId> entries = persistenceEntryManager.findEntries(staticConfiguration.getBaseDn().getSessions(), SessionId.class, Filter.createEqualityFilter("deviceSecret", deviceSecret));
if (entries == null || entries.size() != 1) {
return null;
}
return entries.get(0);
}

@Nullable
public SessionId getSessionByDn(@Nullable String dn, boolean silently) {
if (StringUtils.isBlank(dn)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.jans.as.server.authorize.ws.rs;

import io.jans.as.common.model.session.SessionId;
import io.jans.as.common.util.RedirectUri;
import io.jans.as.model.config.WebKeysConfiguration;
import io.jans.as.model.configuration.AppConfiguration;
import io.jans.as.model.crypto.AbstractCryptoProvider;
import io.jans.as.model.error.ErrorResponseFactory;
import io.jans.as.server.model.authorize.ScopeChecker;
import io.jans.as.server.par.ws.rs.ParService;
import io.jans.as.server.service.ClientService;
import io.jans.as.server.service.RedirectUriResponse;
import io.jans.as.server.service.RedirectionUriService;
import io.jans.as.server.service.RequestParameterService;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
* @author Yuriy Z
*/
@Listeners(MockitoTestNGListener.class)
public class AuthzRequestServiceTest {

@InjectMocks
private AuthzRequestService authzRequestService;

@Mock
private Logger log;

@Mock
private AppConfiguration appConfiguration;

@Mock
private ErrorResponseFactory errorResponseFactory;

@Mock
private AuthorizeRestWebServiceValidator authorizeRestWebServiceValidator;

@Mock
private ParService parService;

@Mock
private AbstractCryptoProvider cryptoProvider;

@Mock
private ScopeChecker scopeChecker;

@Mock
private RequestParameterService requestParameterService;

@Mock
private WebKeysConfiguration webKeysConfiguration;

@Mock
private ClientService clientService;

@Mock
private RedirectionUriService redirectionUriService;

@Test
public void addDeviceSecretToSession_withoutDeviceSsoScope_shouldNotGenerateDeviceSecret() {
AuthzRequest authzRequest = new AuthzRequest();
authzRequest.setScope("openid");
authzRequest.setRedirectUriResponse(new RedirectUriResponse(mock(RedirectUri.class), "", mock(HttpServletRequest.class), mock(ErrorResponseFactory.class)));

SessionId sessionId = new SessionId();

authzRequestService.addDeviceSecretToSession(authzRequest, sessionId);
assertTrue(sessionId.getDeviceSecrets().isEmpty());
}

@Test
public void addDeviceSecretToSession_withDeviceSsoScope_shouldGenerateDeviceSecret() {
AuthzRequest authzRequest = new AuthzRequest();
authzRequest.setRedirectUriResponse(new RedirectUriResponse(mock(RedirectUri.class), "", mock(HttpServletRequest.class), mock(ErrorResponseFactory.class)));
authzRequest.setScope("openid device_sso");

SessionId sessionId = new SessionId();

assertTrue(sessionId.getDeviceSecrets().isEmpty());
authzRequestService.addDeviceSecretToSession(authzRequest, sessionId);
assertEquals(1, sessionId.getDeviceSecrets().size());
assertTrue(StringUtils.isNotBlank(sessionId.getDeviceSecrets().get(0)));
}
}
12 changes: 12 additions & 0 deletions jans-linux-setup/jans_setup/schema/jans_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,17 @@
"syntax": "1.3.6.1.4.1.1466.115.121.1.15",
"x_origin": "Jans created attribute"
},
{
"desc": "deviceSecret",
"equality": "caseIgnoreMatch",
"names": [
"deviceSecret"
],
"oid": "jansAttr",
"substr": "caseIgnoreSubstringsMatch",
"syntax": "1.3.6.1.4.1.1466.115.121.1.15",
"x_origin": "Jans created attribute"
},
{
"desc": "jans Sess DN",
"equality": "caseIgnoreMatch",
Expand Down Expand Up @@ -3625,6 +3636,7 @@
"jansJwt",
"jansPermissionGrantedMap",
"jansInvolvedClnts",
"deviceSecret",
"jansSessAttr"
],
"must": [
Expand Down

0 comments on commit 17a931e

Please sign in to comment.