public static void setTokenCookie( KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) { log.debugf("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE); String accessToken = session.getTokenString(); String idToken = session.getIdTokenString(); String refreshToken = session.getRefreshToken(); String cookie = new StringBuilder(accessToken) .append(DELIM) .append(idToken) .append(DELIM) .append(refreshToken) .toString(); String cookiePath = getContextPath(facade); facade .getResponse() .setCookie( AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true); }
protected boolean verifySSL() { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { log.warn("SSL is required to authenticate"); return true; } return false; }
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie( KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) { OIDCHttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE); if (cookie == null) { log.debug("Not found adapter state cookie in current request"); return null; } String cookieVal = cookie.getValue(); String[] tokens = cookieVal.split(DELIM); if (tokens.length != 3) { log.warnf( "Invalid format of %s cookie. Count of tokens: %s, expected 3", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, tokens.length); return null; } String accessTokenString = tokens[0]; String idTokenString = tokens[1]; String refreshTokenString = tokens[2]; try { // Skip check if token is active now. It's supposed to be done later by the caller AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true); IDToken idToken; if (idTokenString != null && idTokenString.length() > 0) { try { JWSInput input = new JWSInput(idTokenString); idToken = input.readJsonContent(IDToken.class); } catch (JWSInputException e) { throw new VerificationException(e); } } else { idToken = null; } log.debug("Token Verification succeeded!"); RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext( deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString); return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>( AdapterUtils.getPrincipalName(deployment, accessToken), secContext); } catch (VerificationException ve) { log.warn("Failed verify token", ve); return null; } }
protected AuthOutcome handleSamlRequest(String samlRequest, String relayState) { SAMLDocumentHolder holder = null; boolean postBinding = false; String requestUri = facade.getRequest().getURI(); if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) { // strip out query params int index = requestUri.indexOf('?'); if (index > -1) { requestUri = requestUri.substring(0, index); } holder = SAMLRequestParser.parseRequestRedirectBinding(samlRequest); } else { postBinding = true; holder = SAMLRequestParser.parseRequestPostBinding(samlRequest); } RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject(); if (!requestUri.equals(requestAbstractType.getDestination().toString())) { log.error( "expected destination '" + requestUri + "' got '" + requestAbstractType.getDestination() + "'"); return AuthOutcome.FAILED; } if (requestAbstractType instanceof LogoutRequestType) { if (deployment.getIDP().getSingleLogoutService().validateRequestSignature()) { try { validateSamlSignature(holder, postBinding, GeneralConstants.SAML_REQUEST_KEY); } catch (VerificationException e) { log.error("Failed to verify saml request signature", e); return AuthOutcome.FAILED; } } LogoutRequestType logout = (LogoutRequestType) requestAbstractType; return logoutRequest(logout, relayState); } else { log.error("unknown SAML request type"); return AuthOutcome.FAILED; } }
public void verifyRedirectBindingSignature(PublicKey publicKey, String paramKey) throws VerificationException { String request = facade.getRequest().getQueryParamValue(paramKey); String algorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY); String signature = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY); String decodedAlgorithm = facade.getRequest().getQueryParamValue(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY); if (request == null) { throw new VerificationException("SAML Request was null"); } if (algorithm == null) throw new VerificationException("SigAlg was null"); if (signature == null) throw new VerificationException("Signature was null"); // Shibboleth doesn't sign the document for redirect binding. // todo maybe a flag? String relayState = facade.getRequest().getQueryParamValue(GeneralConstants.RELAY_STATE); KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("/").queryParam(paramKey, request); if (relayState != null) { builder.queryParam(GeneralConstants.RELAY_STATE, relayState); } builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm); String rawQuery = builder.build().getRawQuery(); try { // byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature); byte[] decodedSignature = Base64.decode(signature); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm); Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg validator.initVerify(publicKey); validator.update(rawQuery.getBytes("UTF-8")); if (!validator.verify(decodedSignature)) { throw new VerificationException("Invalid query param signature"); } } catch (Exception e) { throw new VerificationException(e); } }
@Override public void saveRequest() { HttpSession session = request.getSession(true); session.setAttribute(REDIRECT_URI, facade.getRequest().getURI()); session.setAttribute(SAVED_METHOD, request.getMethod()); MultivaluedHashMap<String, String> headers = new MultivaluedHashMap<>(); Enumeration<String> names = request.getHeaderNames(); while (names.hasMoreElements()) { String name = names.nextElement(); Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { headers.add(name.toLowerCase(), values.nextElement()); } } session.setAttribute(SAVED_HEADERS, headers); if (request.getMethod().equalsIgnoreCase("GET")) { return; } ByteArrayOutputStream os = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int bytesRead; int totalRead = 0; try { InputStream is = request.getInputStream(); while ((bytesRead = is.read(buffer)) >= 0) { os.write(buffer); totalRead += bytesRead; if (totalRead > maxBuffer) { throw new RuntimeException("max buffer reached on a saved request"); } } } catch (IOException e) { throw new RuntimeException(e); } byte[] body = os.toByteArray(); // Only save the request body if there is something to save if (body.length > 0) { session.setAttribute(SAVED_BODY, body); } }
private static String getContextPath(HttpFacade facade) { String uri = facade.getRequest().getURI(); String path = KeycloakUriBuilder.fromUri(uri).getPath(); int index = path.indexOf("/", 1); return index == -1 ? path : path.substring(0, index); }
public static void removeCookie(HttpFacade facade) { String cookiePath = getContextPath(facade); facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath); }
protected AuthOutcome handleLoginResponse( ResponseType responseType, OnSessionCreated onCreateSession) { AssertionType assertion = null; try { assertion = AssertionUtil.getAssertion(responseType, deployment.getDecryptionKey()); if (AssertionUtil.hasExpired(assertion)) { return initiateLogin(); } } catch (Exception e) { log.error("Error extracting SAML assertion: " + e.getMessage()); challenge = new AuthChallenge() { @Override public boolean challenge(HttpFacade exchange) { SamlAuthenticationError error = new SamlAuthenticationError(SamlAuthenticationError.Reason.EXTRACTION_FAILURE); exchange.getRequest().setError(error); exchange.getResponse().sendError(403); return true; } @Override public int getResponseCode() { return 403; } }; } SubjectType subject = assertion.getSubject(); SubjectType.STSubType subType = subject.getSubType(); NameIDType subjectNameID = (NameIDType) subType.getBaseID(); String principalName = subjectNameID.getValue(); final Set<String> roles = new HashSet<>(); MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>(); Set<StatementAbstractType> statements = assertion.getStatements(); for (StatementAbstractType statement : statements) { if (statement instanceof AttributeStatementType) { AttributeStatementType attributeStatement = (AttributeStatementType) statement; List<AttributeStatementType.ASTChoiceType> attList = attributeStatement.getAttributes(); for (AttributeStatementType.ASTChoiceType obj : attList) { AttributeType attr = obj.getAttribute(); if (isRole(attr)) { List<Object> attributeValues = attr.getAttributeValue(); if (attributeValues != null) { for (Object attrValue : attributeValues) { String role = getAttributeValue(attrValue); log.debugv("Add role: {0}", role); roles.add(role); } } } else { List<Object> attributeValues = attr.getAttributeValue(); if (attributeValues != null) { for (Object attrValue : attributeValues) { String value = getAttributeValue(attrValue); if (attr.getName() != null) { attributes.add(attr.getName(), value); } if (attr.getFriendlyName() != null) { friendlyAttributes.add(attr.getFriendlyName(), value); } } } } } } } if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE) { if (deployment.getPrincipalAttributeName() != null) { String attribute = attributes.getFirst(deployment.getPrincipalAttributeName()); if (attribute != null) principalName = attribute; else { attribute = friendlyAttributes.getFirst(deployment.getPrincipalAttributeName()); if (attribute != null) principalName = attribute; } } } AuthnStatementType authn = null; for (Object statement : assertion.getStatements()) { if (statement instanceof AuthnStatementType) { authn = (AuthnStatementType) statement; break; } } URI nameFormat = subjectNameID.getFormat(); String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString(); final SamlPrincipal principal = new SamlPrincipal( assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes); String index = authn == null ? null : authn.getSessionIndex(); final String sessionIndex = index; SamlSession account = new SamlSession(principal, roles, sessionIndex); sessionStore.saveAccount(account); onCreateSession.onSessionCreated(account); // redirect to original request, it will be restored String redirectUri = sessionStore.getRedirectUri(); if (redirectUri != null) { facade.getResponse().setHeader("Location", redirectUri); facade.getResponse().setStatus(302); facade.getResponse().end(); } else { log.debug("IDP initiated invocation"); } log.debug("AUTHENTICATED authn"); return AuthOutcome.AUTHENTICATED; }
protected AuthOutcome handleSamlResponse( String samlResponse, String relayState, OnSessionCreated onCreateSession) { SAMLDocumentHolder holder = null; boolean postBinding = false; String requestUri = facade.getRequest().getURI(); if (facade.getRequest().getMethod().equalsIgnoreCase("GET")) { int index = requestUri.indexOf('?'); if (index > -1) { requestUri = requestUri.substring(0, index); } holder = extractRedirectBindingResponse(samlResponse); } else { postBinding = true; holder = extractPostBindingResponse(samlResponse); } final StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject(); // validate destination if (!requestUri.equals(statusResponse.getDestination())) { log.error("Request URI does not match SAML request destination"); return AuthOutcome.FAILED; } if (statusResponse instanceof ResponseType) { try { if (deployment.getIDP().getSingleSignOnService().validateResponseSignature()) { try { validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); } catch (VerificationException e) { log.error("Failed to verify saml response signature", e); challenge = new AuthChallenge() { @Override public boolean challenge(HttpFacade exchange) { SamlAuthenticationError error = new SamlAuthenticationError( SamlAuthenticationError.Reason.INVALID_SIGNATURE); exchange.getRequest().setError(error); exchange.getResponse().sendError(403); return true; } @Override public int getResponseCode() { return 403; } }; return AuthOutcome.FAILED; } } return handleLoginResponse((ResponseType) statusResponse, onCreateSession); } finally { sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); } } else { if (sessionStore.isLoggingOut()) { try { if (deployment.getIDP().getSingleLogoutService().validateResponseSignature()) { try { validateSamlSignature(holder, postBinding, GeneralConstants.SAML_RESPONSE_KEY); } catch (VerificationException e) { log.error("Failed to verify saml response signature", e); return AuthOutcome.FAILED; } } return handleLogoutResponse(holder, statusResponse, relayState); } finally { sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); } } else if (sessionStore.isLoggingIn()) { try { // KEYCLOAK-2107 - handle user not authenticated due passive mode. Return special outcome // so different authentication mechanisms can behave accordingly. StatusType status = statusResponse.getStatus(); if (checkStatusCodeValue( status.getStatusCode(), JBossSAMLURIConstants.STATUS_RESPONDER.get()) && checkStatusCodeValue( status.getStatusCode().getStatusCode(), JBossSAMLURIConstants.STATUS_NO_PASSIVE.get())) { log.debug( "Not authenticated due passive mode Status found in SAML response: " + status.toString()); return AuthOutcome.NOT_AUTHENTICATED; } challenge = new AuthChallenge() { @Override public boolean challenge(HttpFacade exchange) { SamlAuthenticationError error = new SamlAuthenticationError( SamlAuthenticationError.Reason.ERROR_STATUS, statusResponse); exchange.getRequest().setError(error); exchange.getResponse().sendError(403); return true; } @Override public int getResponseCode() { return 403; } }; return AuthOutcome.FAILED; } finally { sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.NONE); } } return AuthOutcome.NOT_ATTEMPTED; } }