KEYCLOAK-18499 Add max_age support to oauth2 brokered logins
Revise KcOidcBrokerPassMaxAgeTest to use setTimeOffset(...)
This commit is contained in:
parent
a3f339a1c4
commit
43623ea9d0
7 changed files with 125 additions and 3 deletions
|
@ -30,6 +30,7 @@ public class IdentityProviderModel implements Serializable {
|
||||||
|
|
||||||
public static final String ALLOWED_CLOCK_SKEW = "allowedClockSkew";
|
public static final String ALLOWED_CLOCK_SKEW = "allowedClockSkew";
|
||||||
public static final String LOGIN_HINT = "loginHint";
|
public static final String LOGIN_HINT = "loginHint";
|
||||||
|
public static final String PASS_MAX_AGE = "passMaxAge";
|
||||||
|
|
||||||
public static final String SYNC_MODE = "syncMode";
|
public static final String SYNC_MODE = "syncMode";
|
||||||
|
|
||||||
|
@ -230,6 +231,14 @@ public class IdentityProviderModel implements Serializable {
|
||||||
getConfig().put(LOGIN_HINT, String.valueOf(loginHint));
|
getConfig().put(LOGIN_HINT, String.valueOf(loginHint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPassMaxAge() {
|
||||||
|
return Boolean.valueOf(getConfig().get(PASS_MAX_AGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassMaxAge(boolean passMaxAge) {
|
||||||
|
getConfig().put(PASS_MAX_AGE, String.valueOf(passMaxAge));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isHideOnLogin() {
|
public boolean isHideOnLogin() {
|
||||||
return Boolean.valueOf(getConfig().get(HIDE_ON_LOGIN));
|
return Boolean.valueOf(getConfig().get(HIDE_ON_LOGIN));
|
||||||
|
|
|
@ -331,6 +331,11 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
||||||
uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
|
uriBuilder.queryParam(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String maxAge = request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.MAX_AGE_PARAM);
|
||||||
|
if (getConfig().isPassMaxAge() && maxAge != null) {
|
||||||
|
uriBuilder.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
|
||||||
|
}
|
||||||
|
|
||||||
if (getConfig().isUiLocales()) {
|
if (getConfig().isUiLocales()) {
|
||||||
uriBuilder.queryParam(OIDCLoginProtocol.UI_LOCALES_PARAM, session.getContext().resolveLocale(null).toLanguageTag());
|
uriBuilder.queryParam(OIDCLoginProtocol.UI_LOCALES_PARAM, session.getContext().resolveLocale(null).toLanguageTag());
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,16 +121,17 @@ public class AbstractOAuth2IdentityProviderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestProvider getTested() {
|
private TestProvider getTested() {
|
||||||
return new TestProvider(getConfig(null, null, null, Boolean.FALSE));
|
return new TestProvider(getConfig(null, null, null, Boolean.FALSE, Boolean.FALSE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2IdentityProviderConfig getConfig(final String autorizationUrl, final String defaultScope, final String clientId, final Boolean isLoginHint) {
|
private OAuth2IdentityProviderConfig getConfig(final String autorizationUrl, final String defaultScope, final String clientId, final Boolean isLoginHint, final Boolean passMaxAge) {
|
||||||
IdentityProviderModel model = new IdentityProviderModel();
|
IdentityProviderModel model = new IdentityProviderModel();
|
||||||
OAuth2IdentityProviderConfig config = new OAuth2IdentityProviderConfig(model);
|
OAuth2IdentityProviderConfig config = new OAuth2IdentityProviderConfig(model);
|
||||||
config.setAuthorizationUrl(autorizationUrl);
|
config.setAuthorizationUrl(autorizationUrl);
|
||||||
config.setDefaultScope(defaultScope);
|
config.setDefaultScope(defaultScope);
|
||||||
config.setClientId(clientId);
|
config.setClientId(clientId);
|
||||||
config.setLoginHint(isLoginHint);
|
config.setLoginHint(isLoginHint);
|
||||||
|
config.setPassMaxAge(passMaxAge);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,7 +292,7 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
|
||||||
/**
|
/**
|
||||||
* Get the login page for an existing client in provided realm
|
* Get the login page for an existing client in provided realm
|
||||||
*
|
*
|
||||||
* @param contextRoot
|
* @param contextRoot server base url without /auth
|
||||||
* @param realmName Name of the realm
|
* @param realmName Name of the realm
|
||||||
* @param clientId ClientId of a client. Client has to exists in the realm.
|
* @param clientId ClientId of a client. Client has to exists in the realm.
|
||||||
* @return Login URL
|
* @return Login URL
|
||||||
|
@ -303,6 +303,9 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
|
||||||
assertThat(clients, Matchers.is(Matchers.not(Matchers.empty())));
|
assertThat(clients, Matchers.is(Matchers.not(Matchers.empty())));
|
||||||
|
|
||||||
String redirectURI = clients.get(0).getBaseUrl();
|
String redirectURI = clients.get(0).getBaseUrl();
|
||||||
|
if (redirectURI.startsWith("/")) {
|
||||||
|
redirectURI = contextRoot + "/auth" + redirectURI;
|
||||||
|
}
|
||||||
|
|
||||||
return contextRoot + "/auth/realms/" + realmName + "/protocol/openid-connect/auth?client_id=" +
|
return contextRoot + "/auth/realms/" + realmName + "/protocol/openid-connect/auth?client_id=" +
|
||||||
clientId + "&redirect_uri=" + redirectURI + "&response_type=code&scope=openid";
|
clientId + "&redirect_uri=" + redirectURI + "&response_type=code&scope=openid";
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
import org.keycloak.models.IdentityProviderSyncMode;
|
||||||
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
|
import org.keycloak.testsuite.Assert;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_ALIAS;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestConstants.IDP_OIDC_PROVIDER_ID;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.createIdentityProvider;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
|
||||||
|
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the propagation of the max_age parameter for brokered logins.
|
||||||
|
*
|
||||||
|
* see https://issues.redhat.com/browse/KEYCLOAK-18499
|
||||||
|
*/
|
||||||
|
public class KcOidcBrokerPassMaxAgeTest extends AbstractBrokerTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BrokerConfiguration getBrokerConfiguration() {
|
||||||
|
return new KcOidcBrokerConfigurationWithPassMaxAge();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class KcOidcBrokerConfigurationWithPassMaxAge extends KcOidcBrokerConfiguration {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
|
||||||
|
IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID);
|
||||||
|
|
||||||
|
Map<String, String> config = idp.getConfig();
|
||||||
|
applyDefaultConfiguration(config, syncMode);
|
||||||
|
config.put(IdentityProviderModel.LOGIN_HINT, "false");
|
||||||
|
|
||||||
|
config.put(IdentityProviderModel.PASS_MAX_AGE, "true");
|
||||||
|
|
||||||
|
return idp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void testLogInAsUserInIDP() {
|
||||||
|
// super.testLogInAsUserInIDP();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Override
|
||||||
|
public void loginWithExistingUser() {
|
||||||
|
|
||||||
|
// login as brokered user user, perform profile update on first broker login and logout user
|
||||||
|
loginUser();
|
||||||
|
testSingleLogout();
|
||||||
|
|
||||||
|
driver.navigate().to(getAccountUrl(getConsumerRoot(), bc.consumerRealmName()));
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||||
|
|
||||||
|
loginPage.login(bc.getUserLogin(), bc.getUserPassword());
|
||||||
|
accountUpdateProfilePage.assertCurrent();
|
||||||
|
|
||||||
|
setTimeOffset(2);
|
||||||
|
|
||||||
|
// trigger re-auth with max_age while we are still authenticated
|
||||||
|
String loginUrlWithMaxAge = getLoginUrl(getConsumerRoot(), bc.consumerRealmName(), "account") + "&max_age=1";
|
||||||
|
driver.navigate().to(loginUrlWithMaxAge);
|
||||||
|
|
||||||
|
// we should now see the login page of the consumer
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
loginPage.assertCurrent(bc.consumerRealmName());
|
||||||
|
Assert.assertTrue("Driver should be on the consumer realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.consumerRealmName() + "/protocol/openid-connect/auth"));
|
||||||
|
|
||||||
|
loginPage.clickSocial(bc.getIDPAlias());
|
||||||
|
// we should see the login page of the provider, since the max_age was propagated
|
||||||
|
waitForPage(driver, "sign in to", true);
|
||||||
|
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||||
|
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||||
|
loginPage.assertCurrent(bc.providerRealmName());
|
||||||
|
|
||||||
|
// reauthenticate with password
|
||||||
|
loginPage.login(bc.getUserPassword());
|
||||||
|
waitForPage(driver, "account management", true);
|
||||||
|
|
||||||
|
testSingleLogout();
|
||||||
|
}
|
||||||
|
}
|
|
@ -718,6 +718,8 @@ token-url=Token URL
|
||||||
token-url.tooltip=The Token URL.
|
token-url.tooltip=The Token URL.
|
||||||
loginHint=Pass login_hint
|
loginHint=Pass login_hint
|
||||||
loginHint.tooltip=Pass login_hint to identity provider.
|
loginHint.tooltip=Pass login_hint to identity provider.
|
||||||
|
passMaxAge=Pass max_age
|
||||||
|
passMaxAge.tooltip=Pass max_age to identity provider.
|
||||||
uiLocales=Pass current locale
|
uiLocales=Pass current locale
|
||||||
uiLocales.tooltip=Pass the current locale to the identity provider as a ui_locales parameter.
|
uiLocales.tooltip=Pass the current locale to the identity provider as a ui_locales parameter.
|
||||||
logout-url=Logout URL
|
logout-url=Logout URL
|
||||||
|
|
|
@ -139,6 +139,13 @@
|
||||||
</div>
|
</div>
|
||||||
<kc-tooltip>{{:: 'loginHint.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'loginHint.tooltip' | translate}}</kc-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label" for="passMaxAge">{{:: 'passMaxAge' | translate}}</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input ng-model="identityProvider.config.passMaxAge" id="passMaxAge" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'passMaxAge.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-2 control-label" for="uiLocales">{{:: 'uiLocales' | translate}}</label>
|
<label class="col-sm-2 control-label" for="uiLocales">{{:: 'uiLocales' | translate}}</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
|
|
Loading…
Reference in a new issue