Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-04-17 16:41:16 -04:00
commit 8eb2f35423
87 changed files with 2193 additions and 274 deletions

View file

@ -35,10 +35,43 @@
<addColumn tableName="CREDENTIAL">
<column name="CREATED_DATE" type="BIGINT"/>
</addColumn>
<createTable tableName="GRANTED_CONSENT">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="CLIENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="GRANTED_CONSENT_ROLE">
<column name="GRANTED_CONSENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="ROLE_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="GRANTED_CONSENT_PROT_MAPPER">
<column name="GRANTED_CONSENT_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_IDPM" tableName="IDENTITY_PROVIDER_MAPPER"/>
<addPrimaryKey columnNames="IDP_MAPPER_ID, NAME" constraintName="CONSTRAINT_IDPMConfig" tableName="IDP_MAPPER_CONFIG"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GRNTCSNT_PM" tableName="GRANTED_CONSENT"/>
<addPrimaryKey columnNames="GRANTED_CONSENT_ID, ROLE_ID" constraintName="CONSTRAINT_GRNTCSNT_ROLE_PM" tableName="GRANTED_CONSENT_ROLE"/>
<addPrimaryKey columnNames="GRANTED_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTRAINT_GRNTCSNT_PRM_PM" tableName="GRANTED_CONSENT_PROT_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="IDENTITY_PROVIDER_MAPPER" constraintName="FK_IDPM_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="IDP_MAPPER_ID" baseTableName="IDP_MAPPER_CONFIG" constraintName="FK_IDPMConfig" referencedColumnNames="ID" referencedTableName="IDENTITY_PROVIDER_MAPPER"/>
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="GRANTED_CONSENT" constraintName="FK_GRNTCSNT_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
<addForeignKeyConstraint baseColumnNames="GRANTED_CONSENT_ID" baseTableName="GRANTED_CONSENT_ROLE" constraintName="FK_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="GRANTED_CONSENT"/>
<addForeignKeyConstraint baseColumnNames="GRANTED_CONSENT_ID" baseTableName="GRANTED_CONSENT_PROT_MAPPER" constraintName="FK_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="GRANTED_CONSENT"/>
<addColumn tableName="CLIENT">
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
@ -68,6 +101,7 @@
<dropUniqueConstraint tableName="KEYCLOAK_ROLE" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2"/>
<addUniqueConstraint columnNames="NAME,CLIENT_REALM_CONSTRAINT" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2-2" tableName="KEYCLOAK_ROLE"/>
<addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT" tableName="GRANTED_CONSENT"/>
</changeSet>
</databaseChangeLog>

View file

@ -20,6 +20,9 @@
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
<class>org.keycloak.models.jpa.entities.GrantedConsentEntity</class>
<class>org.keycloak.models.jpa.entities.GrantedConsentRoleEntity</class>
<class>org.keycloak.models.jpa.entities.GrantedConsentProtocolMapperEntity</class>
<!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>

View file

@ -18,7 +18,8 @@ public class UriUtils {
public static String getOrigin(String uri) {
String u = uri.toString();
return u.substring(0, u.indexOf('/', 8));
int e = u.indexOf('/', 8);
return e != -1 ? u.substring(0, u.indexOf('/', 8)) : u;
}
public static boolean isOrigin(String url) {

View file

@ -80,14 +80,14 @@ HttpClient client = new HttpClientBuilder()
try {
HttpPost post = new HttpPost(
KeycloakUriBuilder.fromUri("http://localhost:8080/auth")
.path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build("demo"));
.path(ServiceUrlConstants.TOKEN_PATH).build("demo"));
List <NameValuePair> formparams = new ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "password"));
formparams.add(new BasicNameValuePair("username", "bburke"));
formparams.add(new BasicNameValuePair("password", "password"));
if (isPublic()) { // if client is public access type
formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "password"));
} else {
String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
post.setHeader("Authorization", authorization);

View file

@ -215,10 +215,11 @@ ktadd -k /tmp/http.keytab HTTP/www.mydomain.org@MYDOMAIN.ORG
The scenario is supported by Keycloak, but there is tricky thing that SPNEGO authentication is done by Keycloak server but
GSS credential will need to be used by your application. So you need to enable built-in <literal>gss delegation credential</literal> protocol mapper
in admin console for your application. This will cause that Keycloak will deserialize GSS credential and transmit it to the application
in access token. Application will need to deserialize it and use it for further GSS calls against other services.
in access token. Application will need to deserialize it and use it for further GSS calls against other services. We have an example, which is showing it in details. It's in <literal>examples/kerberos</literal>
in the Keycloak appliance distribution or WAR distribution download. You can also check the example sources directly <ulink url="https://github.com/keycloak/keycloak/blob/master/examples/kerberos">here</ulink> .
</para>
<para>
GSSContext will need to
Once you deserialize the credential from the access token to the GSSCredential object, then GSSContext will need to
be created with this credential passed to the method <literal>GSSManager.createContext</literal> for example like this:
<programlisting><![CDATA[
GSSContext context = gssManager.createContext(serviceName, krb5Oid,
@ -227,7 +228,7 @@ GSSContext context = gssManager.createContext(serviceName, krb5Oid,
</para>
<para>
Note that you also need to configure <literal>forwardable</literal> kerberos tickets in <literal>krb5.conf</literal> file
and add support for delegated credentials to your browser. See the kerberos example from Keycloak example set for details.
and add support for delegated credentials to your browser. For details, see the kerberos example from Keycloak examples set as mentioned above.
</para>
<warning>
<para>

View file

@ -128,9 +128,10 @@
<para>
In the admin console, per realm, you can set up a password policy to enforce that users pick hard to guess passwords.
A password has to match all policies. The password policies that can be configured are hash iterations, length, digits,
lowercase, uppercase, special characters, not username, regex patterns and expired passwords. Expired Passwords policy
lowercase, uppercase, special characters, not username, regex patterns, password history and force expired password update.
Force expired password update policy forces or requires password updates after specified span of time. Password history policy
restricts a user from resetting his password to N old expired passwords. Multiple regex patterns, separated by comma,
can be specified. If there's more than one regex added, password has to match all fully.
can be specified in regex pattern policy. If there's more than one regex added, password has to match all fully.
Increasing number of Hash Iterations (n) does not worsen anything (and certainly not the cipher) and it greatly increases the
resistance to dictionary attacks. However the drawback to increasing n is that it has some cost (CPU usage, energy, delay) for
the legitimate parties. Increasing n also slightly increases the odds that a random password gives the same result as the right

View file

@ -826,4 +826,36 @@ All configuration options are optional. Default value for directory is <literal>
<link linkend='themes'>Themes</link> sections for more information on how to do this.
</para>
</section>
<section>
<title>Installing Keycloak Server as Root Context</title>
<para>
The Keycloak server can be installed as the default web application. This way, instead of referencing
the server as <literal>http://mydomain.com/auth</literal>, it would be
<literal>http://mydomain.com/</literal>.
</para>
<para>
To do this, you need to add a <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
<programlisting><![CDATA[
<subsystem xmlns="urn:jboss:domain:undertow:1.2">
<server name="default-server">
<host name="default-host" alias="localhost" default-web-module="main-auth-server.war">
<location name="/" handler="welcome-content"/>
</host>
]]></programlisting>
</para>
<para>
<literal>main-auth-server</literal> is the name of the Keycloak server as defined in the Keycloak subsystem.
</para>
<para>
<note>
If you have already run your server before changing to the root context then your database
will contain references to the old /auth context. And, your clients may also have incorrect
references. To fix this on the server side, you will need to <link linkend="export-import">export
your database to json, make corrections, and then import.</link> Client-side keycloak.json
files will need to be updated manually as well.
</note>
</para>
</section>
</chapter>

View file

@ -23,5 +23,6 @@ public interface Details {
String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id";
String NODE_HOST = "node_host";
String REASON = "reason";
String REVOKED_CLIENT = "revoked_client";
}

View file

@ -39,6 +39,8 @@ public enum EventType {
REMOVE_TOTP(true),
REMOVE_TOTP_ERROR(true),
REVOKE_GRANT(true),
SEND_VERIFY_EMAIL(true),
SEND_VERIFY_EMAIL_ERROR(true),
SEND_RESET_PASSWORD(true),

View file

@ -92,6 +92,9 @@ public class LDAPIdentityStoreRegistry {
case LDAPConstants.VENDOR_TIVOLI:
uniqueIdentifierAttributeName = "uniqueidentifier";
break;
case LDAPConstants.VENDOR_NOVELL_EDIRECTORY:
uniqueIdentifierAttributeName = "guid";
break;
case LDAPConstants.VENDOR_ACTIVE_DIRECTORY:
uniqueIdentifierAttributeName = LDAPConstants.OBJECT_GUID;
}

View file

@ -5,6 +5,6 @@ package org.keycloak.account;
*/
public enum AccountPages {
ACCOUNT, PASSWORD, TOTP, FEDERATED_IDENTITY, LOG, SESSIONS;
ACCOUNT, PASSWORD, TOTP, FEDERATED_IDENTITY, LOG, SESSIONS, ACCESS;
}

View file

@ -20,6 +20,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.keycloak.account.AccountPages;
import org.keycloak.account.AccountProvider;
import org.keycloak.account.freemarker.model.AccessBean;
import org.keycloak.account.freemarker.model.AccountBean;
import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
import org.keycloak.account.freemarker.model.FeaturesBean;
@ -37,6 +38,7 @@ import org.keycloak.freemarker.FreeMarkerUtil;
import org.keycloak.freemarker.LocaleHelper;
import org.keycloak.freemarker.Theme;
import org.keycloak.freemarker.ThemeProvider;
import org.keycloak.freemarker.beans.AdvancedMessageFormatterMethod;
import org.keycloak.freemarker.beans.LocaleBean;
import org.keycloak.freemarker.beans.MessageBean;
import org.keycloak.freemarker.beans.MessageFormatterMethod;
@ -183,6 +185,10 @@ public class FreeMarkerAccountProvider implements AccountProvider {
case SESSIONS:
attributes.put("sessions", new SessionsBean(realm, sessions));
break;
case ACCESS:
attributes.put("access", new AccessBean(realm, user, uriInfo.getBaseUri(), stateChecker));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
break;
case PASSWORD:
attributes.put("password", new PasswordBean(passwordSet));
}

View file

@ -21,6 +21,8 @@ public class Templates {
return "log.ftl";
case SESSIONS:
return "sessions.ftl";
case ACCESS:
return "access.ftl";
default:
throw new IllegalArgumentException();
}

View file

@ -0,0 +1,85 @@
package org.keycloak.account.freemarker.model;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.util.MultivaluedHashMap;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AccessBean {
private List<ClientGrantBean> clientGrants = new LinkedList<ClientGrantBean>();
public AccessBean(RealmModel realm, UserModel user, URI baseUri, String stateChecker) {
List<GrantedConsentModel> grantedConsents = user.getGrantedConsents();
for (GrantedConsentModel consent : grantedConsents) {
ClientModel client = realm.getClientById(consent.getClientId());
List<RoleModel> realmRolesGranted = new LinkedList<RoleModel>();
MultivaluedHashMap<String, RoleModel> resourceRolesGranted = new MultivaluedHashMap<String, RoleModel>();
for (String roleId : consent.getGrantedRoles()) {
RoleModel role = realm.getRoleById(roleId);
if (role.getContainer() instanceof RealmModel) {
realmRolesGranted.add(role);
} else {
resourceRolesGranted.add(((ClientModel) role.getContainer()).getClientId(), role);
}
}
List<String> claimsGranted = new LinkedList<String>();
for (String protocolMapperId : consent.getGrantedProtocolMappers()) {
ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protocolMapperId);
claimsGranted.add(protocolMapper.getConsentText());
}
ClientGrantBean clientGrant = new ClientGrantBean(realmRolesGranted, resourceRolesGranted, client, claimsGranted);
clientGrants.add(clientGrant);
}
}
public List<ClientGrantBean> getClientGrants() {
return clientGrants;
}
public static class ClientGrantBean {
private final List<RoleModel> realmRolesGranted;
private final MultivaluedHashMap<String, RoleModel> resourceRolesGranted;
private final ClientModel client;
private final List<String> claimsGranted;
public ClientGrantBean(List<RoleModel> realmRolesGranted, MultivaluedHashMap<String, RoleModel> resourceRolesGranted,
ClientModel client, List<String> claimsGranted) {
this.realmRolesGranted = realmRolesGranted;
this.resourceRolesGranted = resourceRolesGranted;
this.client = client;
this.claimsGranted = claimsGranted;
}
public List<RoleModel> getRealmRolesGranted() {
return realmRolesGranted;
}
public MultivaluedHashMap<String, RoleModel> getResourceRolesGranted() {
return resourceRolesGranted;
}
public ClientModel getClient() {
return client;
}
public List<String> getClaimsGranted() {
return claimsGranted;
}
}
}

View file

@ -59,6 +59,10 @@ public class UrlBean {
return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString();
}
public String getRevokeClientUrl() {
return Urls.accountRevokeClientPage(baseQueryURI, realm).toString();
}
public String getTotpRemoveUrl() {
return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString();
}

View file

@ -0,0 +1,55 @@
<#import "template.ftl" as layout>
<@layout.mainLayout active='access' bodyClass='access'; section>
<div class="row">
<div class="col-md-10">
<h2>${msg("accessHtmlTitle")}</h2>
</div>
</div>
<form action="${url.revokeClientUrl}" method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
<table class="table table-striped table-bordered">
<thead>
<tr>
<td>${msg("client")}</td>
<td>${msg("grantedPersonalInfo")}</td>
<td>${msg("grantedPermissions")}</td>
<td>${msg("action")}</td>
</tr>
</thead>
<tbody>
<#list access.clientGrants as clientGrant>
<tr>
<td><#if clientGrant.client.baseUrl??><a href="${clientGrant.client.baseUrl}">${clientGrant.client.clientId}</a><#else>${clientGrant.client.clientId}</#if></td>
<td>
<#list clientGrant.claimsGranted as claim>
${advancedMsg(claim)}<#if claim_has_next>, </#if>
</#list>
</td>
<td>
<#list clientGrant.realmRolesGranted as role>
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
<#if role_has_next>, </#if>
</#list>
<#list clientGrant.resourceRolesGranted?keys as resource>
<#if clientGrant.realmRolesGranted?has_content>, </#if>
<#list clientGrant.resourceRolesGranted[resource] as role>
<#if role.description??>${advancedMsg(role.description)}<#else>${advancedMsg(role.name)}</#if>
${msg("inResource", resource)}
<#if role_has_next>, </#if>
</#list>
</#list>
</td>
<td>
<button type='submit' class='btn btn-primary' id='revoke-${clientGrant.client.clientId}' name='clientId' value="${clientGrant.client.id}">${msg("revoke")}</button>
</td>
</tr>
</#list>
</tbody>
</table>
</form>
</@layout.mainLayout>

View file

@ -1,82 +0,0 @@
<#-- TODO: Only a placeholder, implementation needed -->
<#import "template.ftl" as layout>
<@layout.mainLayout active='access' bodyClass='access'; section>
<#if section = "header">
<h2>Manage Authorised Access</h2>
<#elseif section = "content">
<p class="info">Services requested access to your following accounts:</p>
<table class="list">
<caption>Table of services</caption>
<tbody>
<tr class="collapsed">
<td class="provider"><a href="#eventjuggler">EventJuggler</a></td>
<td class="soft">3 services accessing</td>
</tr>
<tr class="expanded hidden" id="#eventjuggler">
<td colspan="2">
<span class="provider">EventJuggler</span>
<p>You have granted the following services access to your EventJuggler account:</p>
<form>
<ul>
<li>
<span class="item">Event Announcer - Events info</span>
<button class="link"><span class="icon-cancel-circle">Icon: remove</span>Revoke Access</button>
</li>
<li>
<span class="item">Facebook - Events info</span>
<button class="link"><span class="icon-cancel-circle">Icon: remove</span>Revoke Access</button>
</li>
<li>
<span class="item">Event Announcer - Profile info</span>
<span class="status red">Access revoked</span>
<button class="link">Undo</button>
</li>
</ul>
<div class="form-actions">
<button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button>
</div>
</form>
</td>
</tr>
<tr class="collapsed">
<td class="provider"><a href="#another-service">Another Service</a></td>
<td class="soft">5 services accessing</td>
</tr>
<tr class="expanded hidden" id="another-service">
<td colspan="2">
<span class="provider">EventJuggler</span>
<p>You have granted the following services access to your EventJuggler account:</p>
<form>
<ul>
<li>
<span class="item">Event Announcer - Events info</span>
<button class="link"><span class="icon-cancel-circle">Icon: remove</span>Revoke Access</button>
</li>
<li>
<span class="item">Facebook - Events info</span>
<button class="link"><span class="icon-cancel-circle">Icon: remove</span>Revoke Access</button>
</li>
<li>
<span class="item">Event Announcer - Profile info</span>
<span class="status red">Access revoked</span>
<button class="link">Undo</button>
</li>
</ul>
<div class="form-actions">
<button type="submit" class="primary">Save</button>
<button type="submit">Cancel</button>
</div>
</form>
</td>
</tr>
</tbody>
</table>
</#if>
</@layout.mainLayout>

View file

@ -99,5 +99,6 @@ invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster
invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort: darf nicht gleich einem der letzten {0} Passwortgeschichte.
locale_de=Deutsch
locale_en=Englisch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)

View file

@ -12,20 +12,45 @@ changePasswordHtmlTitle=Change Password
sessionsHtmlTitle=Sessions
accountManagementTitle=Keycloak Account Management
authenticatorTitle=Authenticator
accessHtmlTitle=Manage Granted Permissions
authenticatorCode=One-time code
email=Email
firstName=First name
givenName=Given name
fullName=Full name
lastName=Last name
familyName=Family name
password=Password
passwordConfirm=Confirmation
passwordNew=New Password
username=Username
address=Address
street=Street
locality=City or Locality
region=State, Province, or Region
postal_code=Zip or Postal code
country=Country
emailVerified=Email verified
gssDelegationCredential=gss delegation credential
role_admin=Admin
role_realm-admin=Realm Admin
role_create-realm=Create realm
role_view-realm=View realm
role_view-users=View users
role_view-applications=View applications
role_view-clients=View clients
role_view-events=View events
role_view-identity-providers=View identity providers
role_manage-realm=Manage realm
role_manage-users=Manage users
role_manage-applications=Manage applications
role_manage-identity-providers=Manage identity providers
role_manage-clients=Manage clients
role_manage-events=Manage events
role_view-profile=View profile
requiredFields=Required fields
allFieldsRequired=All fields required
@ -49,6 +74,13 @@ federatedIdentity=Federated Identity
authenticator=Authenticator
sessions=Sessions
log=Log
access=Access
grantedPersonalInfo=Granted Personal Info
grantedPermissions=Granted Permissions
action=Action
inResource=in <strong>{0}</strong>
revoke=Revoke Access
configureAuthenticators=Configured Authenticators
mobile=Mobile
@ -74,6 +106,8 @@ readOnlyPasswordMessage=You can''t update your password as your account is read
successTotpMessage=Mobile authenticator configured.
successTotpRemovedMessage=Mobile authenticator removed.
successGrantRevokedMessage=Access revoked successfully.
accountUpdatedMessage=Your account has been updated.
accountPasswordUpdatedMessage=Your password has been updated.
@ -99,4 +133,5 @@ invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)

View file

@ -0,0 +1,103 @@
doSave=Salva
doCancel=Annulla
doLogOutAllSessions=Effettua Log out in tutte le sessioni
doRemove=Elimina
doAdd=Aggiungi
doSignOut=Sign Out
editAccountHtmlTtile=Gestisci Account
federatedIdentitiesHtmlTitle=Federated Identities
accountLogHtmlTitle=Account Log
changePasswordHtmlTitle=Cambia Password
sessionsHtmlTitle=Sessioni
accountManagementTitle=Keycloak Account Management
authenticatorTitle=Authenticator
authenticatorCode=Codice One-time
email=Email
firstName=Nome
lastName=Cognome
password=Password
passwordConfirm=Conferma Password
passwordNew=Nuova Password
username=Username
street=Via
locality=Citta'' o Localita''
region=Stato, Provincia, o Regione
postal_code=Cap
country=Paese
requiredFields=Campi obbligatori
allFieldsRequired=Tutti campi obbligatori
backToApplication=&laquo; Torna all''applicazione
backTo=Torna a {0}
date=Data
event=Evento
ip=IP
client=Client
clients=Client
details=Dettagli
started=Iniziato
lastAccess=Ultimo Accesso
expires=Scade
applications=Applicazioni
account=Account
federatedIdentity=Federated Identity
authenticator=Authenticator
sessions=Sessioni
log=Log
configureAuthenticators=Authenticator configurati
mobile=Mobile
totpStep1=Installa <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> o <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> sul tuo dispositivo mobile.
totpStep2=Apri l''applicazione e scansiona il barcode o scrivi la chiave.
totpStep3=Scrivi il codice one-time fornito dall''applicazione e clicca Salva per completare il setup.
missingFirstNameMessage=Inserisci il nome.
invalidEmailMessage=Invalid email address.
missingLastNameMessage=Inserisci il cognome.
missingEmailMessage=Inserisci l''indirizzo email.
missingPasswordMessage=Inserisci la password.
notMatchPasswordMessage=La password non coincide.
missingTotpMessage=Inserisci il codice di autenticazione.
invalidPasswordExistingMessage=Password esistente non valida.
invalidPasswordConfirmMessage=La password di conferma non coincide.
invalidTotpMessage=Codice di autenticazione non valido.
readOnlyUserMessage=Non puoi aggiornare il tuo account dal momento che e'' in sola lettura.
readOnlyPasswordMessage=Non puoi aggiornare la tua password dal momento che e'' in sola lettura.
successTotpMessage=Mobile authenticator configurato.
successTotpRemovedMessage=Mobile authenticator rliminato.
accountUpdatedMessage=Il tuo account e'' stato aggiornato.
accountPasswordUpdatedMessage=La tua password e'' stata aggiornata.
missingIdentityProviderMessage=Identity provider non specificata.
invalidFederatedIdentityActionMessage=Azione non valida o mancante.
identityProviderNotFoundMessage=L''identity provider specificato non e'' stato trovato.
federatedIdentityLinkNotActiveMessage=Questo identity non e'' piu'' attivo.
federatedIdentityRemovingLastProviderMessage=Non puoi rimuovere l''ultimo federated identity dal momento che non hai piu'' la password.
identityProviderRedirectErrorMessage=Fallito il redirect all''identity provider.
identityProviderRemovedMessage=Identity provider eliminato con successo.
accountDisabledMessage=Account disabilitato, contatta l''amministratore.
accountTemporarilyDisabledMessage=L''account e'' temporaneamente disabilitato, contatta l''admin o riprova piu'' tardi.
invalidPasswordMinLengthMessage=Password non valida: lunghezza minima {0}.
invalidPasswordMinLowerCaseCharsMessage=Password non valida: deve contenere almeno {0} caratteri minuscoli.
invalidPasswordMinDigitsMessage=Password non valida: deve contenere almeno {0} numeri.
invalidPasswordMinUpperCaseCharsMessage=Password non valida: deve contenere almeno {0} caratteri maiuscoli.
invalidPasswordMinSpecialCharsMessage=Password non valida: deve contenere almeno {0} caratteri speciali.
invalidPasswordNotUsernameMessage=Password non valida: non deve essere uguale allo username.
invalidPasswordRegexPatternMessage=Password non valida: fallito il match con una o piu'' espressioni regolari.
invalidPasswordHistoryMessage=Password non valida: non deve ssere uguale ad una delle ultime {0} password.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)

View file

@ -100,4 +100,5 @@ invalidPasswordHistoryMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual a qu
locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR)

View file

@ -56,6 +56,7 @@
<li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">${msg("authenticator")}</a></li>
<#if features.identityFederation><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">${msg("federatedIdentity")}</a></li></#if>
<li class="<#if active=='sessions'>active</#if>"><a href="${url.sessionsUrl}">${msg("sessions")}</a></li>
<li class="<#if active=='access'>active</#if>"><a href="${url.accessUrl}">${msg("access")}</a></li>
<#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">${msg("log")}</a></li></#if>
</ul>
</div>

View file

@ -64,7 +64,7 @@
<div data-ng-include data-src="resourceUrl + '/partials/menu.html'"></div>
</header>
<div class="container" data-ng-show="auth.hasAnyAccess">
<div class="container">
<div data-ng-view id="view"></div>
<div id="loading" class="loading-backdrop">
<div class="loading">

View file

@ -960,8 +960,6 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'ProtocolListCtrl'
})
.when('/server-info', {
templateUrl : resourceUrl + '/partials/server-info.html'
})
@ -969,8 +967,14 @@ module.config([ '$routeProvider', function($routeProvider) {
templateUrl : resourceUrl + '/partials/home.html',
controller : 'LogoutCtrl'
})
.otherwise({
.when('/notfound', {
templateUrl : resourceUrl + '/partials/notfound.html'
})
.when('/forbidden', {
templateUrl : resourceUrl + '/partials/forbidden.html'
})
.otherwise({
templateUrl : resourceUrl + '/partials/pagenotfound.html'
});
} ]);
@ -994,29 +998,6 @@ module.config(function($httpProvider) {
});
module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Notifications, Auth) {
return function(promise) {
return promise.then(function(response) {
return response;
}, function(response) {
if (response.status == 401) {
Auth.authz.logout();
} else if (response.status == 403) {
Notifications.error("Forbidden");
} else if (response.status == 404) {
Notifications.error("Not found");
} else if (response.status) {
if (response.data && response.data.errorMessage) {
Notifications.error(response.data.errorMessage);
} else {
Notifications.error("An unexpected server error has occurred");
}
}
return $q.reject(response);
});
};
});
module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location) {
return function(promise) {
return promise.then(function(response) {
@ -1044,6 +1025,29 @@ module.factory('spinnerInterceptor', function($q, $window, $rootScope, $location
};
});
module.factory('errorInterceptor', function($q, $window, $rootScope, $location, Notifications, Auth) {
return function(promise) {
return promise.then(function(response) {
return response;
}, function(response) {
if (response.status == 401) {
Auth.authz.logout();
} else if (response.status == 403) {
$location.path('/forbidden');
} else if (response.status == 404) {
$location.path('/notfound');
} else if (response.status) {
if (response.data && response.data.errorMessage) {
Notifications.error(response.data.errorMessage);
} else {
Notifications.error("An unexpected server error has occurred");
}
}
return $q.reject(response);
});
};
});
// collapsable form fieldsets
module.directive('collapsable', function() {
return function(scope, element, attrs) {

View file

@ -121,12 +121,8 @@ module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $l
$scope.changeRealm = function(selectedRealm) {
$location.url("/realms/" + selectedRealm);
};
$scope.showNav = function() {
var show = Current.realms.length > 0;
return Auth.loggedIn && show;
}
$scope.refresh = function() {
Current.refresh();
}
@ -399,7 +395,7 @@ module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serv
$scope.supportedLocalesOptions = {
'multiple' : true,
'simple_tags' : true,
'tags' : ['en', 'de', 'pt-BR']
'tags' : ['en', 'de', 'pt-BR', 'it']
};
$scope.$watch('realm.supportedLocales', function(oldVal, newVal) {

View file

@ -547,6 +547,7 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog,
{ "id": "ad", "name": "Active Directory" },
{ "id": "rhds", "name": "Red Hat Directory Server" },
{ "id": "tivoli", "name": "Tivoli" },
{ "id": "edirectory", "name": "Novell eDirectory" },
{ "id": "other", "name": "Other" }
];

View file

@ -908,7 +908,8 @@ module.factory('PasswordPolicy', function() {
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
notUsername: "Block passwords that are equal to the username",
regexPatterns: "Block passwords that do not match all of the regex patterns (string type).",
passwordHistory: "Block passwords that are equal to previous passwords. Default value is 3."
passwordHistory: "Block passwords that are equal to previous passwords. Default value is 3.",
forceExpiredPasswordChange: "Force password change when password credential is expired. Default value is 365 days."
}
p.allPolicies = [
@ -920,7 +921,8 @@ module.factory('PasswordPolicy', function() {
{ name: 'specialChars', value: 1 },
{ name: 'notUsername', value: 1 },
{ name: 'regexPatterns', value: ''},
{ name: 'passwordHistory', value: 3 }
{ name: 'passwordHistory', value: 3 },
{ name: 'forceExpiredPasswordChange', value: 365 }
];
p.parse = function(policyString) {

View file

@ -178,12 +178,11 @@
<span tooltip-placement="right" tooltip="Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed i.e. 'http://example.com/*'. Relative path can be specified too i.e. /my/relative/path/*. Relative paths will generate a redirect URI using the request's host and port. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="!client.bearerOnly && !create">
<label class="col-sm-2 control-label" for="baseUrl">Default Redirect URL</label>
<label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
<div class="col-sm-6">
<input class="form-control" type="text" name="baseUrl" id="baseUrl"
data-ng-model="client.baseUrl">
<input class="form-control" type="text" name="baseUrl" id="baseUrl" data-ng-model="client.baseUrl">
</div>
<span tooltip-placement="right" tooltip="Default URL to use when the auth server needs to redirect back to the client. This URL will also be used when the auth server needs to link to the client for any reason." class="fa fa-info-circle"></span>
<span tooltip-placement="right" tooltip="Default URL to use when the auth server needs to redirect or link back to the client." class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-hide="create || protocol == 'saml'">
<label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>

View file

@ -0,0 +1,7 @@
<div id="content-area" class="col-sm-12" role="main">
<div class="error-container">
<h2>Forbidden</h2>
<p class="instruction">You don't have access to the requested resource.</p>
<a href="#" class="link-right">Go to the home page &raquo;</a>
</div>
</div>

View file

@ -26,10 +26,10 @@
</li>
</ul>
<ul class="nav navbar-nav navbar-primary persistent-secondary" data-ng-controller="RealmDropdownCtrl">
<li class="dropdown context" data-ng-show="showNav()">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<li class="dropdown context" data-ng-show="current.realm.realm">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{{current.realm.realm}}
<b class="caret"></b>
<b class="caret" data-ng-show="current.realms.length > 1"></b>
</a>
<ul class="dropdown-menu" data-ng-show="current.realms.length > 1">
<li data-ng-repeat="realm in current.realms" data-ng-if="realm.realm != current.realm.realm">
@ -37,6 +37,17 @@
</li>
</ul>
</li>
<li class="dropdown context" data-ng-show="!current.realm.realm">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Select realm...
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li data-ng-repeat="realm in current.realms">
<a href="" ng-click="changeRealm(realm.realm)">{{realm.realm}}</a>
</li>
</ul>
</li>
<li class="active pull-right" data-ng-show="auth.user && access.createRealm">
<a class="button primary" href="#/create/realm" data-ng-class="path[0] == 'create' && path[1] == 'realm' && 'active'"
data-ng-show="auth.user">Add Realm</a>
@ -45,6 +56,7 @@
</div>
</div>
</nav>
<!-- TODO remove once this page is properly styled -->
<style type="text/css">
.icon-spinner6 {

View file

@ -1,14 +1,7 @@
<div id="wrapper" class="container">
<div class="row">
<div class="bs-sidebar col-md-3 clearfix"></div>
<div id="content-area" class="col-md-9" role="main">
<div class="error-container">
<h2>Page <strong>not found</strong>...</h2>
<p class="instruction">We could not find the page you are looking for. Please make sure the URL you entered is correct.</p>
<a href="#" class="link-right">Go to the home page &raquo;</a>
<!-- <a href="#" class="link-right">Go to the realm page &raquo;</a> -->
</div>
</div>
<div id="container-right-bg"></div>
<div id="content-area" class="col-sm-12" role="main">
<div class="error-container">
<h2>Resource <strong>not found</strong>...</h2>
<p class="instruction">We could not find the resource you are looking for. Please make sure the URL you entered is correct.</p>
<a href="#" class="link-right">Go to the home page &raquo;</a>
</div>
</div>

View file

@ -0,0 +1,7 @@
<div id="content-area" class="col-sm-12" role="main">
<div class="error-container">
<h2>Page <strong>not found</strong>...</h2>
<p class="instruction">We could not find the page you are looking for. Please make sure the URL you entered is correct.</p>
<a href="#" class="link-right">Go to the home page &raquo;</a>
</div>
</div>

View file

@ -7,6 +7,9 @@
<#elseif section = "form">
<div id="kc-error-message">
<p class="instruction">${message.summary}</p>
<#if client?? && client.baseUrl?has_content>
<p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
</#if>
</div>
</#if>
</@layout.registrationLayout>

View file

@ -1,4 +1,3 @@
<#-- TODO: Only a placeholder, implementation needed -->
<#import "template.ftl" as layout>
<@layout.registrationLayout bodyClass="oauth"; section>
<#if section = "title">

View file

@ -174,5 +174,6 @@ clientNotFoundMessage=Client not found.
emailVerifiedMessage=Ihr E-Mail Adresse wurde erfolgreich verifiziert.
locale_de=Deutsch
locale_en=Englisch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)

View file

@ -157,8 +157,7 @@ couldNotSendAuthenticationRequestMessage=Could not send authentication request t
unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider [{0}].
invalidAccessCodeMessage=Invalid access code.
sessionNotActiveMessage=Session not active.
unknownCodeMessage=Unknown code, please login again through your application.
invalidCodeMessage=Invalid code, please login again through your application.
invalidCodeMessage=An error occurred, please login again through your application.
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
identityProviderNotFoundMessage=Could not find an identity provider with the identifier [{0}].
realmSupportsNoCredentialsMessage=Realm [{0}] does not support any credential type.
@ -167,6 +166,7 @@ emailVerifiedMessage=Your email address has been verified.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
backToApplication=&laquo; Back to Application

View file

@ -0,0 +1,175 @@
doLogIn=Accedi
doRegister=Registrati
doCancel=Annulla
doSubmit=Invia
doYes=Si
doNo=No
doForgotPassword=Password Dimenticata?
doClickHere=Clicca qui
registerWithTitle=Registrati come {0}
registerWithTitleHtml=Registrati come <strong>{0}</strong>
loginTitle=Accedi a {0}
loginTitleHtml=Accedi a <strong>{0}</strong>
loginTotpTitle=Configura Autenticazione Mobile
loginProfileTitle=Aggiorna Profilo
oauthGrantTitle=OAuth Grant
oauthGrantTitleHtml=Accesso temporaneo per <strong>{0}</strong> richiesto da <strong>{1}</strong>.
errorTitle=Siamo spiacenti...
errorTitleHtml=Siamo <strong>spiacenti</strong> ...
emailVerifyTitle=Verifica Email
emailForgotTitle=Password Dimenticata?
updatePasswordTitle=Modifica Password
codeSuccessTitle=Codice di Successo
codeErrorTitle=Codice di Errore\: {0}
noAccount=Nuovo Utente?
username=Username
usernameOrEmail=Username o email
firstName=Nome
givenName=Nome
fullName=Nome Completo
lastName=Cognome
familyName=Cognome
email=Email
password=Password
passwordConfirm=Conferma password
passwordNew=Nuova Password
passwordNewConfirm=Conferma nuova password
rememberMe=Ricordami
authenticatorCode=Codice One-time
address=Indirizzo
street=Via
locality=Citta'' o Localita''
region=Stato, Provincia, o Regione
postal_code=Cap
country=Paese
emailVerified=Email verificata
gssDelegationCredential=credenziali gss delegation
loginTotpStep1=Installa <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> sul tuo dispositivo mobile
loginTotpStep2=Apri l''applicazione e scansione il barcode o scrivi la chiave
loginTotpStep3=Scrivi il codice one-time fornito dall''applicazione e premi Invia per finire il setup
loginTotpOneTime=Codice one-time
oauthGrantRequest=Vuoi assegnare questi privilegi di accesso?
inResource=per <strong>{0}</strong>
emailVerifyInstruction1=Ti e'' stata inviata una email con le istruzioni per la verifica della tua email.
emailVerifyInstruction2=Non hai ricevuto un codice di verifica nella tua email?
emailVerifyInstruction3=per reinviare la mail.
backToLogin=&laquo; Torna al Login
emailInstruction=Scrivi il tuo username o indirizzo email e noi ti invieremo le istruzioni per creare una nuova password.
copyCodeInstruction=Copiaquesto codice e incollalo nella tua applicazione:
personalInfo=Informazioni personali:
role_admin=Admin
role_realm-admin=Realm Admin
role_create-realm=Crea realm
role_view-realm=Visualizza realm
role_view-users=Visualizza utenti
role_view-applications=Visualizza applicazioni
role_view-clients=Visualizza client
role_view-events=Visualizza eventi
role_view-identity-providers=Visualizza identity provider
role_manage-realm=Gestisci realm
role_manage-users=Gestisci utenti
role_manage-applications=Gestisci applicazioni
role_manage-identity-providers=Gestisci identity provider
role_manage-clients=Gestisci client
role_manage-events=Gestisci eventi
role_view-profile=Visualizza profilo
role_manage-account=Gestisci account
invalidUserMessage=Username o password non valida.
invalidEmailMessage=Indirizzo email non valido.
accountDisabledMessage=Account disabilitato, contatta l''admin.
accountTemporarilyDisabledMessage=Account temporaneamente disabilitato, contatta l''admin o riprova piu'' tardi.
expiredCodeMessage=Login timeout. Effettua login di nuovo.
missingFirstNameMessage=Inserisci il nome.
missingLastNameMessage=Inserisci il cognome.
missingEmailMessage=Inserisci l''indirizzo email.
missingUsernameMessage=Inserisci lo username.
missingPasswordMessage=Inserisci la password.
missingTotpMessage=Inserisci il codice di autenticazione.
notMatchPasswordMessage=Le passwords non coincidono.
invalidPasswordExistingMessage=Password esistente non valida.
invalidPasswordConfirmMessage=La password di conferma non coincide.
invalidTotpMessage=Codice di autenticazione non valido.
usernameExistsMessage=Username gia'' esistente.
emailExistsMessage=Email gia'' esistente.
federatedIdentityEmailExistsMessage=Utente con email gia'' esistente. Effettua il login nella gestione account per associare l''account.
federatedIdentityUsernameExistsMessage=Utente con username gia'' esistente.Effettua il login nella gestione account per associare l''account.
configureTotpMessage=Devi impostare un Mobile Authenticator per attivare il tuo account.
updateProfileMessage=Devi aggiornare il tuo profilo utente per attivare il tuo account.
updatePasswordMessage=Devi cambiare la password per attivare il tuo account.
verifyEmailMessage=Devi verificare il tuo indirizzo email per attivare il tuo account.
emailSentMessage=Riceverai a breve una email con maggiori istruzioni.
emailSendErrorMessage=Invio email fallito, riptoav piu'' tardi.
accountUpdatedMessage=Il tuo account e'' stato aggiornato.
accountPasswordUpdatedMessage=La tua password e'' stata aggiornata.
noAccessMessage=Nessun accesso
invalidPasswordMinLengthMessage=Password non valida: lunghezza minima {0}.
invalidPasswordMinDigitsMessage=Password non valida: deve contenere almeno {0} numeri.
invalidPasswordMinLowerCaseCharsMessage=Password non valida: deve contenere almeno {0} caratteri minuscoli.
invalidPasswordMinUpperCaseCharsMessage=Password non valida: deve contenere almeno {0} caratteri maiuscoli.
invalidPasswordMinSpecialCharsMessage=Password non valida: deve contenere almeno {0} caratteri speciali.
invalidPasswordNotUsernameMessage=Password non valida: non deve essere uguale allo username.
invalidPasswordRegexPatternMessage=Password non valida: fallito il match con una o piu'' espressioni regolari.
invalidPasswordHistoryMessage=Password non valida: non deve ssere uguale ad una delle ultime {0} password.
failedToProcessResponseMessage=Fallimento nell''elaborazione della risposta
httpsRequiredMessage=HTTPS richiesto
realmNotEnabledMessage=Realm non abilitato
invalidRequestMessage=Richiesta non valida
unknownLoginRequesterMessage=Richiedente di Login non riconosciuto
loginRequesterNotEnabledMessage=Richiedente di Login non abilitato
bearerOnlyMessage=Alle applicazioni di tipo Bearer-only non e'' consentito di effettuare il login tramite browser
directGrantsOnlyMessage=Ai client di tipo Direct-grants-only non e'' consentito di effettuare il login tramite browser
invalidRedirectUriMessage=Redirect uri non valido
unsupportedNameIdFormatMessage=NameIDFormat non supportato
invlidRequesterMessage=Richiedente non valido
registrationNotAllowedMessage=Registrazione non permessa
permissionNotApprovedMessage=Permesso non approvato.
noRelayStateInResponseMessage=Nessun relay state in risposta dall''identity provider [{0}].
identityProviderAlreadyLinkedMessage=L''identita'' restituita dall''identity provider [{0}] e'' gia'' associata ad un altro utente.
insufficientPermissionMessage=Permessi insufficienti per associare le identita''.
couldNotProceedWithAuthenticationRequestMessage=Non posso procedere con la richiesta di autenticazione all''identity provider.
couldNotObtainTokenMessage=Non posso ottenere un token dall''identity provider [{0}].
unexpectedErrorRetrievingTokenMessage=Errore inaspettato nella gestione del token dall''identity provider [{0}].
unexpectedErrorHandlingResponseMessage=Errore inaspettato nella gestione della risposta dall''identity provider [{0}].
identityProviderAuthenticationFailedMessage=Autenticazione fallita. Non posso effettuare l''autenticazione con l''identity provider [{0}].
couldNotSendAuthenticationRequestMessage=Non posso inviare la richiesta di autenticazione all''identity provider [{0}].
unexpectedErrorHandlingRequestMessage=Errore imprevisto durante l''autenticazione con identity provider [{0}].
invalidAccessCodeMessage=Codice di accesso non valido.
sessionNotActiveMessage=Sessione non attiva.
invalidCodeMessage=Si e'' verificato un errore, per piacere effettua di nuovo il login nella tua applicazione.
identityProviderUnexpectedErrorMessage=Errore imprevisto durante l''autenticazione con identity provider
identityProviderNotFoundMessage=Non posso trovare un identity provider con l''identificativo [{0}].
realmSupportsNoCredentialsMessage=Il Realm [{0}] non supporta nessun tipo di credenziali.
identityProviderNotUniqueMessage=Il Realm [{0}] supporta piu'' di un identity provider. Non posso determinare quale identity provider con il quale autenticarti.
emailVerifiedMessage=Il tuo indirizzo email e'' stato verificato.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
backToApplication=&laquo; Torna all''Applicazione
missingParameterMessage=Parametri Mancanti\: {0}
clientNotFoundMessage=Client non trovato.
invalidParameterMessage=Parametro non valido\: {0}

View file

@ -167,6 +167,7 @@ emailVerifiedMessage=O seu endere\u00E7o de e-mail foi confirmado.
locale_de=Deutsch
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR)
backToApplication=&laquo; Voltar para a aplica\u00E7\u00E3o

View file

@ -10,6 +10,7 @@ import javax.ws.rs.core.UriInfo;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
@ -41,7 +42,7 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setClientSessionCode(String accessCode);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
public LoginFormsProvider setAccessRequest(String message);
/**

View file

@ -32,6 +32,7 @@ import org.keycloak.login.freemarker.model.UrlBean;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
@ -66,6 +67,7 @@ import java.util.concurrent.TimeUnit;
private Response.Status status;
private List<RoleModel> realmRolesRequested;
private MultivaluedMap<String, RoleModel> resourceRolesRequested;
private List<ProtocolMapperModel> protocolMappersRequested;
private MultivaluedMap<String, String> queryParams;
private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
private String accessRequestMessage;
@ -243,7 +245,7 @@ import java.util.concurrent.TimeUnit;
attributes.put("register", new RegisterBean(formData));
break;
case OAUTH_GRANT:
attributes.put("oauth", new OAuthGrantBean(accessCode, clientSession, client, realmRolesRequested, resourceRolesRequested, this.accessRequestMessage));
attributes.put("oauth", new OAuthGrantBean(accessCode, clientSession, client, realmRolesRequested, resourceRolesRequested, protocolMappersRequested, this.accessRequestMessage));
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
break;
case CODE:
@ -366,9 +368,10 @@ import java.util.concurrent.TimeUnit;
}
@Override
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested) {
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
this.realmRolesRequested = realmRolesRequested;
this.resourceRolesRequested = resourceRolesRequested;
this.protocolMappersRequested = protocolMappersRequested;
return this;
}

View file

@ -42,20 +42,18 @@ public class OAuthGrantBean {
private ClientModel client;
private List<String> claimsRequested;
public OAuthGrantBean(String code, ClientSessionModel clientSession, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, String accessRequestMessage) {
public OAuthGrantBean(String code, ClientSessionModel clientSession, ClientModel client, List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested,
List<ProtocolMapperModel> protocolMappersRequested, String accessRequestMessage) {
this.code = code;
this.client = client;
this.realmRolesRequested = realmRolesRequested;
this.resourceRolesRequested = resourceRolesRequested;
this.accessRequestMessage = accessRequestMessage;
// todo support locale
List<String> claims = new LinkedList<String>();
if (clientSession != null) {
for (ProtocolMapperModel model : client.getProtocolMappers()) {
if (model.isConsentRequired() && model.getProtocol().equals(clientSession.getAuthMethod()) && model.getConsentText() != null) {
claims.add(model.getConsentText());
}
if (protocolMappersRequested != null) {
for (ProtocolMapperModel model : protocolMappersRequested) {
claims.add(model.getConsentText());
}
}
if (claims.size() > 0) this.claimsRequested = claims;

View file

@ -0,0 +1,47 @@
package org.keycloak.models;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GrantedConsentModel {
private final String clientId;
private Set<String> protocolMapperIds = new HashSet<String>();
private Set<String> roleIds = new HashSet<String>();
public GrantedConsentModel(String clientId) {
this.clientId = clientId;
}
public String getClientId() {
return clientId;
}
public void addGrantedRole(String roleId) {
roleIds.add(roleId);
}
public Set<String> getGrantedRoles() {
return roleIds;
}
public boolean isRoleGranted(String roleId) {
return roleIds.contains(roleId);
}
public void addGrantedProtocolMapper(String protocolMapperId) {
protocolMapperIds.add(protocolMapperId);
}
public Set<String> getGrantedProtocolMappers() {
return protocolMapperIds;
}
public boolean isProtocolMapperGranted(String protocolMapperId) {
return protocolMapperIds.contains(protocolMapperId);
}
}

View file

@ -10,6 +10,7 @@ public class LDAPConstants {
public static final String VENDOR_ACTIVE_DIRECTORY = "ad";
public static final String VENDOR_OTHER = "other";
public static final String VENDOR_TIVOLI = "tivoli";
public static final String VENDOR_NOVELL_EDIRECTORY="edirectory" ;
public static final String USERNAME_LDAP_ATTRIBUTE = "usernameLDAPAttribute";
public static final String USER_OBJECT_CLASSES = "userObjectClasses";

View file

@ -78,6 +78,8 @@ public class PasswordPolicy {
list.add(new RegexPatterns(args));
} else if (name.equals(PasswordHistory.NAME)) {
list.add(new PasswordHistory(args));
} else if (name.equals(ForceExpiredPasswordChange.NAME)) {
list.add(new ForceExpiredPasswordChange(args));
}
}
return list;
@ -114,6 +116,22 @@ public class PasswordPolicy {
}
return -1;
}
/**
*
* @return -1 if no force expired password change setting
*/
public int getDaysToExpirePassword() {
if (policies == null)
return -1;
for (Policy p : policies) {
if (p instanceof ForceExpiredPasswordChange) {
return ((ForceExpiredPasswordChange) p).daysToExpirePassword;
}
}
return -1;
}
public Error validate(UserModel user, String password) {
for (Policy p : policies) {
@ -418,6 +436,25 @@ public class PasswordPolicy {
}
}
private static class ForceExpiredPasswordChange implements Policy {
private static final String NAME = "forceExpiredPasswordChange";
private int daysToExpirePassword;
public ForceExpiredPasswordChange(String[] args) {
daysToExpirePassword = intArg(NAME, 365, args);
}
@Override
public Error validate(String username, String password) {
return null;
}
@Override
public Error validate(UserModel user, String password) {
return null;
}
}
private static int intArg(String policy, int defaultValue, String... args) {
if (args == null || args.length == 0) {
return defaultValue;

View file

@ -12,7 +12,7 @@ public class UserCredentialValueModel {
private String device;
private byte[] salt;
private int hashIterations;
private long createdDate;
private Long createdDate;
public String getType() {
return type;
@ -54,11 +54,11 @@ public class UserCredentialValueModel {
this.hashIterations = iterations;
}
public long getCreatedDate() {
public Long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}

View file

@ -320,6 +320,16 @@ public class UserFederationManager implements UserProvider {
session.userStorage().preRemove(realm, role);
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
session.userStorage().preRemove(realm, client);
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
session.userStorage().preRemove(client, protocolMapper);
}
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
if (realm.getPasswordPolicy() != null) {

View file

@ -75,9 +75,11 @@ public interface UserModel {
String getFederationLink();
void setFederationLink(String link);
GrantedConsentModel addGrantedConsent(GrantedConsentModel consent);
GrantedConsentModel getGrantedConsentByClient(String clientId);
List<GrantedConsentModel> getGrantedConsents();
void updateGrantedConsent(GrantedConsentModel consent);
boolean revokeGrantedConsentForClient(String clientId);
public static enum RequiredAction {
VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD

View file

@ -41,6 +41,9 @@ public interface UserProvider extends Provider {
void preRemove(RealmModel realm, RoleModel role);
void preRemove(RealmModel realm, ClientModel client);
void preRemove(ClientModel realm, ProtocolMapperModel protocolMapper);
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input);

View file

@ -11,7 +11,7 @@ public class CredentialEntity {
private String device;
private byte[] salt;
private int hashIterations;
private long createdDate;
private Long createdDate;
private UserEntity user;
@ -63,11 +63,11 @@ public class CredentialEntity {
this.hashIterations = hashIterations;
}
public long getCreatedDate() {
public Long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}

View file

@ -36,6 +36,7 @@ import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.util.UriUtils;
import java.io.IOException;
import java.net.URI;
@ -526,7 +527,7 @@ public class RepresentationToModel {
client.setManagementUrl(resourceRep.getAdminUrl());
if (resourceRep.isSurrogateAuthRequired() != null)
client.setSurrogateAuthRequired(resourceRep.isSurrogateAuthRequired());
client.setBaseUrl(resourceRep.getBaseUrl());
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
@ -576,12 +577,8 @@ public class RepresentationToModel {
Set<String> origins = new HashSet<String>();
for (String redirectUri : resourceRep.getRedirectUris()) {
logger.debugv("add redirect-uri to origin: {0}", redirectUri);
if (redirectUri.startsWith("http:")) {
URI uri = URI.create(redirectUri);
String origin = uri.getScheme() + "://" + uri.getHost();
if (uri.getPort() != -1) {
origin += ":" + uri.getPort();
}
if (redirectUri.startsWith("http")) {
String origin = UriUtils.getOrigin(redirectUri);
logger.debugv("adding default client origin: {0}" , origin);
origins.add(origin);
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.utils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
@ -185,4 +186,29 @@ public class UserModelDelegate implements UserModel {
public void setFederationLink(String link) {
delegate.setFederationLink(link);
}
@Override
public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
return delegate.addGrantedConsent(consent);
}
@Override
public GrantedConsentModel getGrantedConsentByClient(String clientId) {
return delegate.getGrantedConsentByClient(clientId);
}
@Override
public List<GrantedConsentModel> getGrantedConsents() {
return delegate.getGrantedConsents();
}
@Override
public void updateGrantedConsent(GrantedConsentModel consent) {
delegate.updateGrantedConsent(consent);
}
@Override
public boolean revokeGrantedConsentForClient(String clientId) {
return delegate.revokeGrantedConsentForClient(clientId);
}
}

View file

@ -19,6 +19,8 @@ package org.keycloak.models.file;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.file.adapter.UserAdapter;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
@ -384,6 +386,16 @@ public class FileUserProvider implements UserProvider {
// todo not sure what to do for this
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
// TODO
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
// TODO
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
return CredentialValidation.validCredentials(realm, user, input);

View file

@ -16,9 +16,11 @@
*/
package org.keycloak.models.file.adapter;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientModel;
import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@ -31,7 +33,6 @@ import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -43,6 +44,7 @@ import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.entities.FederatedIdentityEntity;
import org.keycloak.models.entities.RoleEntity;
import org.keycloak.models.entities.UserEntity;
import org.keycloak.util.Time;
/**
* UserModel for JSON persistence.
@ -271,7 +273,6 @@ public class UserAdapter implements UserModel, Comparable {
private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
return credentialEntity;
}
@ -285,6 +286,7 @@ public class UserAdapter implements UserModel, Comparable {
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
@ -429,6 +431,35 @@ public class UserAdapter implements UserModel, Comparable {
user.setFederationLink(link);
}
@Override
public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
// TODO
return null;
}
@Override
public GrantedConsentModel getGrantedConsentByClient(String clientId) {
// TODO
return null;
}
@Override
public List<GrantedConsentModel> getGrantedConsents() {
// TODO
return null;
}
@Override
public void updateGrantedConsent(GrantedConsentModel consent) {
// TODO
}
@Override
public boolean revokeGrantedConsentForClient(String clientId) {
// TODO
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -1,8 +1,10 @@
package org.keycloak.models.cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.FederatedIdentityModel;
@ -310,4 +312,14 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
realmInvalidations.add(realm.getId()); // easier to just invalidate whole realm
getDelegate().preRemove(realm, link);
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
getDelegate().preRemove(realm, client);
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(client, protocolMapper);
}
}

View file

@ -1,7 +1,9 @@
package org.keycloak.models.cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.FederatedIdentityModel;
@ -175,4 +177,14 @@ public class NoCacheUserProvider implements CacheUserProvider {
public void preRemove(RealmModel realm, RoleModel role) {
getDelegate().preRemove(realm, role);
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
getDelegate().preRemove(realm, client);
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
getDelegate().preRemove(client, protocolMapper);
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.models.cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@ -274,4 +275,36 @@ public class UserAdapter implements UserModel {
getDelegateForUpdate();
updated.deleteRoleMapping(role);
}
@Override
public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
getDelegateForUpdate();
return updated.addGrantedConsent(consent);
}
@Override
public GrantedConsentModel getGrantedConsentByClient(String clientId) {
// TODO: caching?
getDelegateForUpdate();
return updated.getGrantedConsentByClient(clientId);
}
@Override
public List<GrantedConsentModel> getGrantedConsents() {
// TODO: caching?
getDelegateForUpdate();
return updated.getGrantedConsents();
}
@Override
public void updateGrantedConsent(GrantedConsentModel consent) {
getDelegateForUpdate();
updated.updateGrantedConsent(consent);
}
@Override
public boolean revokeGrantedConsentForClient(String clientId) {
getDelegateForUpdate();
return updated.revokeGrantedConsentForClient(clientId);
}
}

View file

@ -418,6 +418,8 @@ public class ClientAdapter implements ClientModel {
public void removeProtocolMapper(ProtocolMapperModel mapping) {
ProtocolMapperEntity toDelete = getProtocolMapperEntity(mapping.getId());
if (toDelete != null) {
session.users().preRemove(this, mapping);
this.entity.getProtocolMappers().remove(toDelete);
em.remove(toDelete);
}

View file

@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -87,6 +88,9 @@ public class JpaUserProvider implements UserProvider {
private void removeUser(UserEntity user) {
em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteGrantedConsentRolesByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteGrantedConsentProtMappersByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteGrantedConsentsByUser").setParameter("user", user).executeUpdate();
em.remove(user);
}
@ -130,7 +134,13 @@ public class JpaUserProvider implements UserProvider {
@Override
public void preRemove(RealmModel realm) {
int num = em.createNamedQuery("deleteUserRoleMappingsByRealm")
int num = em.createNamedQuery("deleteGrantedConsentRolesByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteGrantedConsentProtMappersByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteGrantedConsentsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserRoleMappingsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserRequiredActionsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
@ -174,9 +184,24 @@ public class JpaUserProvider implements UserProvider {
@Override
public void preRemove(RealmModel realm, RoleModel role) {
em.createNamedQuery("deleteGrantedConsentRolesByRole").setParameter("roleId", role.getId()).executeUpdate();
em.createNamedQuery("deleteUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
em.createNamedQuery("deleteGrantedConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteGrantedConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate();
em.createNamedQuery("deleteGrantedConsentsByClient").setParameter("clientId", client.getId()).executeUpdate();
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
em.createNamedQuery("deleteGrantedConsentProtMappersByProtocolMapper")
.setParameter("protocolMapperId", protocolMapper.getId())
.executeUpdate();
}
@Override
public UserModel getUserById(String id, RealmModel realm) {
TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserById", UserEntity.class);

View file

@ -658,6 +658,8 @@ public class RealmAdapter implements RealmModel {
ClientModel client = getClientById(id);
if (client == null) return false;
session.users().preRemove(this, client);
for (RoleModel role : client.getRoles()) {
client.removeRole(role);
}

View file

@ -1,6 +1,9 @@
package org.keycloak.models.jpa;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@ -9,12 +12,16 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.GrantedConsentEntity;
import org.keycloak.models.jpa.entities.GrantedConsentProtocolMapperEntity;
import org.keycloak.models.jpa.entities.GrantedConsentRoleEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
import org.keycloak.models.jpa.entities.UserRoleMappingEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@ -23,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -273,7 +281,6 @@ public class UserAdapter implements UserModel {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setId(KeycloakModelUtils.generateId());
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
credentialEntity.setUser(user);
return credentialEntity;
@ -288,6 +295,7 @@ public class UserAdapter implements UserModel {
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
@ -471,6 +479,164 @@ public class UserAdapter implements UserModel {
user.setFederationLink(link);
}
@Override
public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
String clientId = consent.getClientId();
if (clientId == null) {
throw new ModelException("clientId needs to be filled for newly added consent!");
}
GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
if (consentEntity != null) {
throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]");
}
consentEntity = new GrantedConsentEntity();
consentEntity.setId(KeycloakModelUtils.generateId());
consentEntity.setUser(user);
consentEntity.setClientId(clientId);
em.persist(consentEntity);
em.flush();
updateGrantedConsentEntity(consentEntity, consent);
return consent;
}
@Override
public GrantedConsentModel getGrantedConsentByClient(String clientId) {
GrantedConsentEntity entity = getGrantedConsentEntity(clientId);
return toConsentModel(entity);
}
@Override
public List<GrantedConsentModel> getGrantedConsents() {
TypedQuery<GrantedConsentEntity> query = em.createNamedQuery("grantedConsentsByUser", GrantedConsentEntity.class);
query.setParameter("userId", getId());
List<GrantedConsentEntity> results = query.getResultList();
List<GrantedConsentModel> consents = new ArrayList<GrantedConsentModel>();
for (GrantedConsentEntity entity : results) {
GrantedConsentModel model = toConsentModel(entity);
consents.add(model);
}
return consents;
}
@Override
public void updateGrantedConsent(GrantedConsentModel consent) {
String clientId = consent.getClientId();
if (clientId == null) {
throw new ModelException("clientId needs to be for newly added consent!");
}
GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
if (consentEntity == null) {
throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]");
}
updateGrantedConsentEntity(consentEntity, consent);
}
@Override
public boolean revokeGrantedConsentForClient(String clientId) {
GrantedConsentEntity consentEntity = getGrantedConsentEntity(clientId);
if (consentEntity == null) return false;
em.remove(consentEntity);
em.flush();
return true;
}
private GrantedConsentEntity getGrantedConsentEntity(String clientId) {
TypedQuery<GrantedConsentEntity> query = em.createNamedQuery("grantedConsentByUserAndClient", GrantedConsentEntity.class);
query.setParameter("userId", getId());
query.setParameter("clientId", clientId);
List<GrantedConsentEntity> results = query.getResultList();
if (results.size() > 1) {
throw new ModelException("More results found for user [" + getUsername() + "] and client [" + clientId + "]");
} else if (results.size() == 1) {
return results.get(0);
} else {
return null;
}
}
private GrantedConsentModel toConsentModel(GrantedConsentEntity entity) {
if (entity == null) {
return null;
}
GrantedConsentModel model = new GrantedConsentModel(entity.getClientId());
Collection<GrantedConsentRoleEntity> grantedRoleEntities = entity.getGrantedRoles();
if (grantedRoleEntities != null) {
for (GrantedConsentRoleEntity grantedRole : grantedRoleEntities) {
model.addGrantedRole(grantedRole.getRoleId());
}
}
Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMapperEntities = entity.getGrantedProtocolMappers();
if (grantedProtocolMapperEntities != null) {
for (GrantedConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) {
model.addGrantedProtocolMapper(grantedProtMapper.getProtocolMapperId());
}
}
return model;
}
// Update roles and protocolMappers to given consentEntity from the consentModel
private void updateGrantedConsentEntity(GrantedConsentEntity consentEntity, GrantedConsentModel consentModel) {
Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers();
Collection<GrantedConsentProtocolMapperEntity> mappersToRemove = new HashSet<GrantedConsentProtocolMapperEntity>(grantedProtocolMapperEntities);
for (String protocolMapperId : consentModel.getGrantedProtocolMappers()) {
GrantedConsentProtocolMapperEntity grantedProtocolMapperEntity = new GrantedConsentProtocolMapperEntity();
grantedProtocolMapperEntity.setGrantedConsent(consentEntity);
grantedProtocolMapperEntity.setProtocolMapperId(protocolMapperId);
// Check if it's already there
if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) {
em.persist(grantedProtocolMapperEntity);
em.flush();
grantedProtocolMapperEntities.add(grantedProtocolMapperEntity);
} else {
mappersToRemove.remove(grantedProtocolMapperEntity);
}
}
// Those mappers were no longer on consentModel and will be removed
for (GrantedConsentProtocolMapperEntity toRemove : mappersToRemove) {
grantedProtocolMapperEntities.remove(toRemove);
em.remove(toRemove);
}
Collection<GrantedConsentRoleEntity> grantedRoleEntities = consentEntity.getGrantedRoles();
Set<GrantedConsentRoleEntity> rolesToRemove = new HashSet<GrantedConsentRoleEntity>(grantedRoleEntities);
for (String roleId : consentModel.getGrantedRoles()) {
GrantedConsentRoleEntity consentRoleEntity = new GrantedConsentRoleEntity();
consentRoleEntity.setGrantedConsent(consentEntity);
consentRoleEntity.setRoleId(roleId);
// Check if it's already there
if (!grantedRoleEntities.contains(consentRoleEntity)) {
em.persist(consentRoleEntity);
em.flush();
grantedRoleEntities.add(consentRoleEntity);
} else {
rolesToRemove.remove(consentRoleEntity);
}
}
// Those roles were no longer on consentModel and will be removed
for (GrantedConsentRoleEntity toRemove : rolesToRemove) {
grantedRoleEntities.remove(toRemove);
em.remove(toRemove);
}
em.flush();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -38,7 +38,7 @@ public class CredentialEntity {
@Column(name="HASH_ITERATIONS")
protected int hashIterations;
@Column(name="CREATED_DATE")
protected long createdDate;
protected Long createdDate;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
@ -100,11 +100,11 @@ public class CredentialEntity {
this.hashIterations = hashIterations;
}
public long getCreatedDate() {
public Long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
public void setCreatedDate(Long createdDate) {
this.createdDate = createdDate;
}

View file

@ -0,0 +1,96 @@
package org.keycloak.models.jpa.entities;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@Entity
@Table(name="GRANTED_CONSENT", uniqueConstraints = {
@UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"})
})
@NamedQueries({
@NamedQuery(name="grantedConsentByUserAndClient", query="select consent from GrantedConsentEntity consent where consent.user.id = :userId and consent.clientId = :clientId"),
@NamedQuery(name="grantedConsentsByUser", query="select consent from GrantedConsentEntity consent where consent.user.id = :userId"),
@NamedQuery(name="deleteGrantedConsentsByRealm", query="delete from GrantedConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId)"),
@NamedQuery(name="deleteGrantedConsentsByUser", query="delete from GrantedConsentEntity consent where consent.user = :user"),
@NamedQuery(name="deleteGrantedConsentsByClient", query="delete from GrantedConsentEntity consent where consent.clientId = :clientId"),
})
public class GrantedConsentEntity {
@Id
@Column(name="ID", length = 36)
protected String id;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;
@Column(name="CLIENT_ID")
protected String clientId;
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "grantedConsent")
Collection<GrantedConsentRoleEntity> grantedRoles = new ArrayList<GrantedConsentRoleEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "grantedConsent")
Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMappers = new ArrayList<GrantedConsentProtocolMapperEntity>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public Collection<GrantedConsentRoleEntity> getGrantedRoles() {
return grantedRoles;
}
public void setGrantedRoles(Collection<GrantedConsentRoleEntity> grantedRoles) {
this.grantedRoles = grantedRoles;
}
public Collection<GrantedConsentProtocolMapperEntity> getGrantedProtocolMappers() {
return grantedProtocolMappers;
}
public void setGrantedProtocolMappers(Collection<GrantedConsentProtocolMapperEntity> grantedProtocolMappers) {
this.grantedProtocolMappers = grantedProtocolMappers;
}
}

View file

@ -0,0 +1,115 @@
package org.keycloak.models.jpa.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NamedQueries({
@NamedQuery(name="deleteGrantedConsentProtMappersByRealm", query=
"delete from GrantedConsentProtocolMapperEntity csm where csm.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId))"),
@NamedQuery(name="deleteGrantedConsentProtMappersByUser", query="delete from GrantedConsentProtocolMapperEntity csm where csm.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.user = :user)"),
@NamedQuery(name="deleteGrantedConsentProtMappersByProtocolMapper", query="delete from GrantedConsentProtocolMapperEntity csm where csm.protocolMapperId = :protocolMapperId)"),
@NamedQuery(name="deleteGrantedConsentProtMappersByClient", query="delete from GrantedConsentProtocolMapperEntity csm where csm.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.clientId = :clientId))"),
})
@Entity
@Table(name="GRANTED_CONSENT_PROT_MAPPER")
@IdClass(GrantedConsentProtocolMapperEntity.Key.class)
public class GrantedConsentProtocolMapperEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name = "GRANTED_CONSENT_ID")
protected GrantedConsentEntity grantedConsent;
@Id
@Column(name="PROTOCOL_MAPPER_ID")
protected String protocolMapperId;
public GrantedConsentEntity getGrantedConsent() {
return grantedConsent;
}
public void setGrantedConsent(GrantedConsentEntity grantedConsent) {
this.grantedConsent = grantedConsent;
}
public String getProtocolMapperId() {
return protocolMapperId;
}
public void setProtocolMapperId(String protocolMapperId) {
this.protocolMapperId = protocolMapperId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GrantedConsentProtocolMapperEntity that = (GrantedConsentProtocolMapperEntity)o;
Key myKey = new Key(this.grantedConsent, this.protocolMapperId);
Key hisKey = new Key(that.grantedConsent, that.protocolMapperId);
return myKey.equals(hisKey);
}
@Override
public int hashCode() {
Key myKey = new Key(this.grantedConsent, this.protocolMapperId);
return myKey.hashCode();
}
public static class Key implements Serializable {
protected GrantedConsentEntity grantedConsent;
protected String protocolMapperId;
public Key() {
}
public Key(GrantedConsentEntity grantedConsent, String protocolMapperId) {
this.grantedConsent = grantedConsent;
this.protocolMapperId = protocolMapperId;
}
public GrantedConsentEntity getGrantedConsent() {
return grantedConsent;
}
public String getProtocolMapperId() {
return protocolMapperId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (grantedConsent != null ? !grantedConsent.getId().equals(key.grantedConsent != null ? key.grantedConsent.getId() : null) : key.grantedConsent != null) return false;
if (protocolMapperId != null ? !protocolMapperId.equals(key.protocolMapperId) : key.protocolMapperId != null) return false;
return true;
}
@Override
public int hashCode() {
int result = grantedConsent != null ? grantedConsent.getId().hashCode() : 0;
result = 31 * result + (protocolMapperId != null ? protocolMapperId.hashCode() : 0);
return result;
}
}
}

View file

@ -0,0 +1,115 @@
package org.keycloak.models.jpa.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@NamedQueries({
@NamedQuery(name="deleteGrantedConsentRolesByRealm", query="delete from GrantedConsentRoleEntity grantedRole where grantedRole.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.user IN (select user from UserEntity user where user.realmId = :realmId))"),
@NamedQuery(name="deleteGrantedConsentRolesByUser", query="delete from GrantedConsentRoleEntity grantedRole where grantedRole.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.user = :user)"),
@NamedQuery(name="deleteGrantedConsentRolesByRole", query="delete from GrantedConsentRoleEntity grantedRole where grantedRole.roleId = :roleId)"),
@NamedQuery(name="deleteGrantedConsentRolesByClient", query="delete from GrantedConsentRoleEntity grantedRole where grantedRole.grantedConsent IN (select consent from GrantedConsentEntity consent where consent.clientId = :clientId)"),
})
@Entity
@Table(name="GRANTED_CONSENT_ROLE")
@IdClass(GrantedConsentRoleEntity.Key.class)
public class GrantedConsentRoleEntity {
@Id
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name = "GRANTED_CONSENT_ID")
protected GrantedConsentEntity grantedConsent;
@Id
@Column(name="ROLE_ID")
protected String roleId;
public GrantedConsentEntity getGrantedConsent() {
return grantedConsent;
}
public void setGrantedConsent(GrantedConsentEntity grantedConsent) {
this.grantedConsent = grantedConsent;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GrantedConsentRoleEntity that = (GrantedConsentRoleEntity)o;
Key myKey = new Key(this.grantedConsent, this.roleId);
Key hisKey = new Key(that.grantedConsent, that.roleId);
return myKey.equals(hisKey);
}
@Override
public int hashCode() {
Key myKey = new Key(this.grantedConsent, this.roleId);
return myKey.hashCode();
}
public static class Key implements Serializable {
protected GrantedConsentEntity grantedConsent;
protected String roleId;
public Key() {
}
public Key(GrantedConsentEntity grantedConsent, String roleId) {
this.grantedConsent = grantedConsent;
this.roleId = roleId;
}
public GrantedConsentEntity getGrantedConsent() {
return grantedConsent;
}
public String getRoleId() {
return roleId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (grantedConsent != null ? !grantedConsent.getId().equals(key.grantedConsent != null ? key.grantedConsent.getId() : null) : key.grantedConsent != null) return false;
if (roleId != null ? !roleId.equals(key.roleId) : key.roleId != null) return false;
return true;
}
@Override
public int hashCode() {
int result = grantedConsent != null ? grantedConsent.getId().hashCode() : 0;
result = 31 * result + (roleId != null ? roleId.hashCode() : 0);
return result;
}
}
}

View file

@ -321,6 +321,8 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
public void removeProtocolMapper(ProtocolMapperModel mapping) {
for (ProtocolMapperEntity entity : getMongoEntity().getProtocolMappers()) {
if (entity.getId().equals(mapping.getId())) {
session.users().preRemove(this, mapping);
getMongoEntity().getProtocolMappers().remove(entity);
updateMongoEntity();
break;

View file

@ -9,6 +9,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -351,6 +352,16 @@ public class MongoUserProvider implements UserProvider {
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
// TODO
}
@Override
public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) {
// TODO
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// todo not sure what to do for this

View file

@ -639,6 +639,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
@Override
public boolean removeClient(String id) {
if (id == null) return false;
ClientModel client = getClientById(id);
if (client == null) return false;
session.users().preRemove(this, client);
return getMongoStore().removeEntity(MongoClientEntity.class, id, invocationContext);
}

View file

@ -4,6 +4,7 @@ import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@ -16,6 +17,7 @@ import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.mongo.utils.MongoModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.util.Time;
import java.util.ArrayList;
import java.util.Collections;
@ -239,7 +241,6 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
private CredentialEntity setCredentials(MongoUserEntity user, UserCredentialModel cred) {
CredentialEntity credentialEntity = new CredentialEntity();
credentialEntity.setType(cred.getType());
credentialEntity.setCreatedDate(new Date().getTime());
credentialEntity.setDevice(cred.getDevice());
return credentialEntity;
}
@ -253,6 +254,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
if (hashIterations == -1)
hashIterations = 1;
}
credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime()));
credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations));
credentialEntity.setSalt(salt);
credentialEntity.setHashIterations(hashIterations);
@ -419,6 +421,35 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
updateUser();
}
@Override
public GrantedConsentModel addGrantedConsent(GrantedConsentModel consent) {
// TODO
return null;
}
@Override
public GrantedConsentModel getGrantedConsentByClient(String clientId) {
// TODO
return null;
}
@Override
public List<GrantedConsentModel> getGrantedConsents() {
// TODO
return null;
}
@Override
public void updateGrantedConsent(GrantedConsentModel consent) {
// TODO
}
@Override
public boolean revokeGrantedConsentForClient(String clientId) {
// TODO
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -107,7 +107,7 @@ public class OIDCLoginProtocolService {
public Object auth() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.init();
return endpoint;
}
/**
@ -117,7 +117,7 @@ public class OIDCLoginProtocolService {
public Object registerPage() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.init().register();
return endpoint.register();
}
/**
@ -127,7 +127,7 @@ public class OIDCLoginProtocolService {
public Object token() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.init();
return endpoint;
}
@Path("login")
@ -135,7 +135,7 @@ public class OIDCLoginProtocolService {
public Object loginPage() {
AuthorizationEndpoint endpoint = new AuthorizationEndpoint(authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OIDCLoginProtocol.CODE_PARAM).init();
return endpoint.legacy(OIDCLoginProtocol.CODE_PARAM);
}
@Path("login-status-iframe.html")
@ -150,7 +150,7 @@ public class OIDCLoginProtocolService {
public Object grantAccessToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.PASSWORD).init();
return endpoint.legacy(OAuth2Constants.PASSWORD);
}
@Path("refresh")
@ -158,7 +158,7 @@ public class OIDCLoginProtocolService {
public Object refreshAccessToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.REFRESH_TOKEN).init();
return endpoint.legacy(OAuth2Constants.REFRESH_TOKEN);
}
@Path("access/codes")
@ -166,7 +166,7 @@ public class OIDCLoginProtocolService {
public Object accessCodeToToken() {
TokenEndpoint endpoint = new TokenEndpoint(tokenManager, authManager, realm, event);
ResteasyProviderFactory.getInstance().injectProperties(endpoint);
return endpoint.legacy(OAuth2Constants.AUTHORIZATION_CODE).init();
return endpoint.legacy(OAuth2Constants.AUTHORIZATION_CODE);
}
@Path("validate")

View file

@ -91,6 +91,25 @@ public class AuthorizationEndpoint {
@GET
public Response build() {
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM);
prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM);
idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT);
checkSsl();
checkRealm();
checkClient();
checkResponseType();
checkRedirectUri();
createClientSession();
switch (action) {
case REGISTER:
return buildRegister();
@ -121,29 +140,6 @@ public class AuthorizationEndpoint {
return this;
}
public AuthorizationEndpoint init() {
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM);
prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM);
idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT);
checkSsl();
checkRealm();
checkClient();
checkResponseType();
checkRedirectUri();
createClientSession();
return this;
}
private void checkSsl() {
if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
event.error(Errors.SSL_REQUIRED);
@ -172,7 +168,7 @@ public class AuthorizationEndpoint {
throw new ErrorPageException(session, Messages.CLIENT_NOT_FOUND );
}
if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
if (client.isBearerOnly()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorPageException(session, Messages.BEARER_ONLY );
}
@ -198,7 +194,9 @@ public class AuthorizationEndpoint {
event.detail(Details.RESPONSE_TYPE, responseType);
if (responseType.equals(OAuth2Constants.CODE)) {
action = Action.CODE;
if (action == null) {
action = Action.CODE;
}
} else {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );

View file

@ -87,6 +87,14 @@ public class TokenEndpoint {
@POST
public Response build() {
formParams = request.getDecodedFormParameters();
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
checkSsl();
checkRealm();
checkGrantType();
checkClient();
switch (action) {
case AUTHORIZATION_CODE:
return buildAuthorizationCodeAccessTokenResponse();
@ -116,18 +124,6 @@ public class TokenEndpoint {
return this;
}
public TokenEndpoint init() {
formParams = request.getDecodedFormParameters();
grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM);
checkSsl();
checkRealm();
checkGrantType();
checkClient();
return this;
}
private void checkSsl() {
if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);

View file

@ -118,6 +118,11 @@ public class Urls {
.build(realmId);
}
public static URI accountRevokeClientPage(URI baseUri, String realmId) {
return accountBase(baseUri).path(AccountService.class, "processRevokeGrant")
.build(realmId);
}
public static URI accountLogout(URI baseUri, URI redirectUri, String realmId) {
return realmLogout(baseUri).queryParam("redirect_uri", redirectUri).build(realmId);
}

View file

@ -14,11 +14,14 @@ import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
@ -41,12 +44,14 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Stateless object that manages authentication
@ -375,6 +380,7 @@ public class AuthenticationManager {
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
RealmModel realm = clientSession.getRealm();
UserModel user = userSession.getUser();
isForcePasswordUpdateRequired(realm, user);
isTotpConfigurationRequired(realm, user);
isEmailVerificationRequired(realm, user);
ClientModel client = clientSession.getClient();
@ -414,9 +420,17 @@ public class AuthenticationManager {
if (client.isConsentRequired()) {
accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT);
GrantedConsentModel grantedConsent = user.getGrantedConsentByClient(client.getId());
List<RoleModel> realmRoles = new LinkedList<RoleModel>();
MultivaluedMap<String, RoleModel> resourceRoles = new MultivaluedMapImpl<String, RoleModel>();
for (RoleModel r : accessCode.getRequestedRoles()) {
// Consent already granted by user
if (grantedConsent != null && grantedConsent.getGrantedRoles().contains(r.getId())) {
continue;
}
if (r.getContainer() instanceof RealmModel) {
realmRoles.add(r);
} else {
@ -424,16 +438,52 @@ public class AuthenticationManager {
}
}
return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles)
.createOAuthGrant(clientSession);
List<ProtocolMapperModel> protocolMappers = new LinkedList<ProtocolMapperModel>();
for (ProtocolMapperModel model : client.getProtocolMappers()) {
if (model.isConsentRequired() && model.getProtocol().equals(clientSession.getAuthMethod()) && model.getConsentText() != null) {
if (grantedConsent == null || !grantedConsent.getGrantedProtocolMappers().contains(model.getId())) {
protocolMappers.add(model);
}
}
}
// Skip grant screen if everything was already approved by this user
if (realmRoles.size() > 0 || resourceRoles.size() > 0 || protocolMappers.size() > 0) {
return session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(accessCode.getCode())
.setAccessRequest(realmRoles, resourceRoles, protocolMappers)
.createOAuthGrant(clientSession);
}
}
event.success();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
}
private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) {
int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword();
if(daysToExpirePassword != -1) {
for (UserCredentialValueModel entity : user.getCredentialsDirectly()) {
if (entity.getType().equals(UserCredentialModel.PASSWORD)) {
if(entity.getCreatedDate() == null) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
logger.debug("User is required to update password");
} else {
long timeElapsed = Time.toMillis(Time.currentTime()) - entity.getCreatedDate();
long timeToExpire = TimeUnit.DAYS.toMillis(daysToExpirePassword);
if(timeElapsed > timeToExpire) {
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
logger.debug("User is required to update password");
}
}
break;
}
}
}
}
protected static void isTotpConfigurationRequired(RealmModel realm, UserModel user) {
for (RequiredCredentialModel c : realm.getRequiredCredentials()) {

View file

@ -143,13 +143,13 @@ public class ClientManager {
public InstallationAdapterConfig toInstallationRepresentation(RealmModel realmModel, ClientModel clientModel, URI baseUri) {
InstallationAdapterConfig rep = new InstallationAdapterConfig();
rep.setAuthServerUrl(baseUri.toString());
rep.setRealm(realmModel.getName());
rep.setRealmKey(realmModel.getPublicKeyPem());
rep.setSslRequired(realmModel.getSslRequired().name().toLowerCase());
if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) rep.setPublicClient(true);
if (clientModel.isBearerOnly()) rep.setBearerOnly(true);
if (!clientModel.isBearerOnly()) rep.setAuthServerUrl(baseUri.toString());
if (clientModel.getRoles().size() > 0) rep.setUseResourceRoleMappings(true);
rep.setResource(clientModel.getClientId());
@ -169,14 +169,12 @@ public class ClientManager {
buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
buffer.append(" <realm>").append(realmModel.getName()).append("</realm>\n");
buffer.append(" <realm-public-key>").append(realmModel.getPublicKeyPem()).append("</realm-public-key>\n");
buffer.append(" <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
if (clientModel.isBearerOnly()){
buffer.append(" <bearer-only>true</bearer-only>\n");
} else {
buffer.append(" <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) {
buffer.append(" <public-client>true</public-client>\n");
}
} else if (clientModel.isPublicClient()) {
buffer.append(" <public-client>true</public-client>\n");
}
buffer.append(" <ssl-required>").append(realmModel.getSslRequired().name()).append("</ssl-required>\n");
buffer.append(" <resource>").append(clientModel.getClientId()).append("</resource>\n");

View file

@ -134,8 +134,6 @@ public class Messages {
public static final String SESSION_NOT_ACTIVE = "sessionNotActiveMessage";
public static final String UNKNOWN_CODE = "unknownCodeMessage";
public static final String INVALID_CODE = "invalidCodeMessage";
public static final String IDENTITY_PROVIDER_UNEXPECTED_ERROR = "identityProviderUnexpectedErrorMessage";
@ -154,6 +152,8 @@ public class Messages {
public static final String SUCCESS_TOTP = "successTotpMessage";
public static final String SUCCESS_GRANT_REVOKED = "successGrantRevokedMessage";
public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProviderMessage";
public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityActionMessage";

View file

@ -41,6 +41,7 @@ import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -75,6 +76,7 @@ import javax.ws.rs.core.Variant;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -348,6 +350,12 @@ public class AccountService {
return forwardToPage("sessions", AccountPages.SESSIONS);
}
@Path("access")
@GET
public Response accessPage() {
return forwardToPage("access", AccountPages.ACCESS);
}
/**
* Check to see if form post has sessionId hidden field and match it against the session id.
*
@ -483,6 +491,46 @@ public class AccountService {
return Response.seeOther(location).build();
}
@Path("revoke-grant")
@POST
public Response processRevokeGrant(final MultivaluedMap<String, String> formData) {
if (auth == null) {
return login("access");
}
require(AccountRoles.MANAGE_ACCOUNT);
csrfCheck(formData);
String clientId = formData.getFirst("clientId");
if (clientId == null) {
return account.setError(Messages.CLIENT_NOT_FOUND).createResponse(AccountPages.ACCESS);
}
ClientModel client = realm.getClientById(clientId);
if (client == null) {
return account.setError(Messages.CLIENT_NOT_FOUND).createResponse(AccountPages.ACCESS);
}
// Revoke grant in UserModel
UserModel user = auth.getUser();
user.revokeGrantedConsentForClient(client.getId());
// Logout clientSessions for this user and client
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, user);
for (UserSessionModel userSession : userSessions) {
List<ClientSessionModel> clientSessions = userSession.getClientSessions();
for (ClientSessionModel clientSession : clientSessions) {
if (clientSession.getClient().getId().equals(clientId)) {
TokenManager.dettachClientSession(session.sessions(), realm, clientSession);
}
}
}
event.event(EventType.REVOKE_GRANT).client(auth.getClient()).user(auth.getUser()).detail(Details.REVOKED_CLIENT, client.getClientId()).success();
setReferrerOnPage();
return account.setSuccess(Messages.SUCCESS_GRANT_REVOKED).createResponse(AccountPages.ACCESS);
}
/**
* Update the TOTP for this account.
*

View file

@ -34,8 +34,10 @@ import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserCredentialModel;
@ -195,7 +197,7 @@ public class LoginActionsService {
clientCode = ClientSessionCode.parse(code, session, realm);
if (clientCode == null) {
event.error(Errors.INVALID_CODE);
response = ErrorPage.error(session, Messages.UNKNOWN_CODE);
response = ErrorPage.error(session, Messages.INVALID_CODE);
return false;
}
session.getContext().setClient(clientCode.getClientSession().getClient());
@ -288,7 +290,7 @@ public class LoginActionsService {
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
if (clientCode == null) {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.UNKNOWN_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
}
ClientSessionModel clientSession = clientCode.getClientSession();
@ -428,7 +430,7 @@ public class LoginActionsService {
ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
if (clientCode == null) {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.UNKNOWN_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
}
if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
event.error(Errors.INVALID_CODE);
@ -576,19 +578,19 @@ public class LoginActionsService {
event.detail(Details.CODE_ID, clientSession.getId());
String redirect = clientSession.getRedirectUri();
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient();
event.client(clientSession.getClient())
.user(clientSession.getUserSession().getUser())
event.client(client)
.user(user)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.REDIRECT_URI, redirect);
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
event.detail(Details.USERNAME, userSession.getLoginUsername());
if (userSession.isRememberMe()) {
event.detail(Details.REMEMBER_ME, "true");
}
event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
event.detail(Details.USERNAME, userSession.getLoginUsername());
if (userSession.isRememberMe()) {
event.detail(Details.REMEMBER_ME, "true");
}
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
@ -607,6 +609,21 @@ public class LoginActionsService {
return protocol.consentDenied(clientSession);
}
GrantedConsentModel grantedConsent = user.getGrantedConsentByClient(client.getId());
if (grantedConsent == null) {
grantedConsent = user.addGrantedConsent(new GrantedConsentModel(client.getId()));
}
for (String roleId : clientSession.getRoles()) {
grantedConsent.addGrantedRole(roleId);
}
// TODO: It's not 100% sure that approved protocolMappers are same like the protocolMappers retrieved here from the client. Maybe clientSession.setProtocolMappers/getProtocolMappers should be added...
for (ProtocolMapperModel protocolMapper : client.getProtocolMappers()) {
if (protocolMapper.isConsentRequired() && protocolMapper.getProtocol().equals(clientSession.getAuthMethod()) && protocolMapper.getConsentText() != null) {
grantedConsent.addGrantedProtocolMapper(protocolMapper.getId());
}
}
user.updateGrantedConsent(grantedConsent);
event.success();
return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
@ -865,7 +882,7 @@ public class LoginActionsService {
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
if (accessCode == null) {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.UNKNOWN_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
}
ClientSessionModel clientSession = accessCode.getClientSession();

View file

@ -21,6 +21,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountAccessPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
@ -87,6 +88,9 @@ public class ProfileTest {
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected AccountAccessPage accountAccessPage;
@WebResource
protected LoginPage loginPage;
@ -186,6 +190,9 @@ public class ProfileTest {
JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
assertEquals("test-user@localhost", profile.getString("username"));
accountAccessPage.open();
accountAccessPage.revokeGrant("third-party");
}
@Test

View file

@ -89,7 +89,7 @@ public abstract class AbstractIdentityProviderTest {
public WebRule webRule = new WebRule(this);
@WebResource
private WebDriver driver;
protected WebDriver driver;
@WebResource
private LoginPage loginPage;
@ -122,6 +122,7 @@ public abstract class AbstractIdentityProviderTest {
@After
public void onAfter() {
revokeGrant();
brokerServerRule.stopSession(this.session, true);
}
@ -387,6 +388,9 @@ public abstract class AbstractIdentityProviderTest {
assertTrue(accountFederatedIdentityPage.isCurrent());
assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\""));
// Revoke grant in account mgmt
revokeGrant();
// Logout from account management
accountFederatedIdentityPage.logout();
assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
@ -401,6 +405,9 @@ public abstract class AbstractIdentityProviderTest {
accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias());
assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\""));
// Revoke grant in account mgmt
revokeGrant();
// Logout from account management
accountFederatedIdentityPage.logout();
assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
@ -637,6 +644,10 @@ public abstract class AbstractIdentityProviderTest {
}
protected void revokeGrant() {
}
protected abstract String getProviderId();
protected IdentityProviderModel getIdentityProviderModel() {

View file

@ -1,19 +1,26 @@
package org.keycloak.testsuite.broker;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.pages.AccountAccessPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testutils.KeycloakServer;
import org.keycloak.util.JsonSerialization;
import org.openqa.selenium.NoSuchElementException;
import java.io.IOException;
import javax.ws.rs.core.UriBuilder;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@ -22,12 +29,14 @@ import static org.junit.Assert.fail;
*/
public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest {
private static final int PORT = 8082;
@ClassRule
public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() {
@Override
protected void configureServer(KeycloakServer server) {
server.getConfig().setPort(8082);
server.getConfig().setPort(PORT);
}
@Override
@ -44,6 +53,25 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderT
@WebResource
private OAuthGrantPage grantPage;
@WebResource
protected AccountAccessPage accountAccessPage;
@Override
protected void revokeGrant() {
String currentUrl = driver.getCurrentUrl();
String accountAccessPath = Urls.accountAccessPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).port(PORT).build(), "realm-with-oidc-identity-provider").toString();
accountAccessPage.setPath(accountAccessPath);
accountAccessPage.open();
try {
accountAccessPage.revokeGrant("broker-app");
} catch (NoSuchElementException e) {
System.err.println("Couldn't revoke broker-app application, maybe because it wasn't granted or user not logged");
}
driver.navigate().to(currentUrl);
}
@Override
protected void doAfterProviderAuthentication() {
// grant access to broker-app

View file

@ -28,7 +28,9 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
@ -39,6 +41,7 @@ import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@ -48,8 +51,10 @@ import org.openqa.selenium.WebDriver;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -93,6 +98,9 @@ public class LoginTest {
@WebResource
protected LoginPage loginPage;
@WebResource
protected LoginPasswordUpdatePage updatePasswordPage;
private static String userId;
@ -219,7 +227,86 @@ public class LoginTest {
events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "sso").assertEvent();
}
@Test
public void loginWithForcePasswordChangePolicy() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
}
});
try {
// Setting offset to more than one day to force password update
// elapsedTime > timeToExpire
Time.setOffset(86405);
loginPage.open();
loginPage.login("login-test", "password");
updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("updatedPassword", "updatedPassword");
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent();
assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
} finally {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy(null));
UserModel user = manager.getSession().users().getUserByUsername("login-test", appRealm);
UserCredentialModel cred = new UserCredentialModel();
cred.setType(CredentialRepresentation.PASSWORD);
cred.setValue("password");
user.updateCredential(cred);
}
});
Time.setOffset(0);
}
}
@Test
public void loginWithoutForcePasswordChangePolicy() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)"));
}
});
try {
// Setting offset to less than one day to avoid forced password update
// elapsedTime < timeToExpire
Time.setOffset(86205);
loginPage.open();
loginPage.login("login-test", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
} finally {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy(null));
}
});
Time.setOffset(0);
}
}
@Test
public void loginNoTimeoutWithLongWait() {
try {

View file

@ -155,7 +155,7 @@ public class ResetPasswordTest {
events.expect(EventType.RESET_PASSWORD_ERROR).client((String) null).user((String) null).error("invalid_code").clearDetails().assertEvent();
assertTrue(errorPage.isCurrent());
assertEquals("Unknown code, please login again through your application.", errorPage.getError());
assertEquals("An error occurred, please login again through your application.", errorPage.getError());
}
@Test
@ -354,7 +354,7 @@ public class ResetPasswordTest {
errorPage.assertCurrent();
assertEquals("Invalid code, please login again through your application.", errorPage.getError());
assertEquals("An error occurred, please login again through your application.", errorPage.getError());
events.expectRequiredAction(EventType.RESET_PASSWORD).error("invalid_code").client((String) null).user((String) null).session((String) null).clearDetails().assertEvent();
} finally {
@ -538,19 +538,35 @@ public class ResetPasswordTest {
}
});
resetPassword("login-test", "password1");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
try {
Time.setOffset(2000000);
resetPassword("login-test", "password1");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
resetPassword("login-test", "password2");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
Time.setOffset(4000000);
resetPassword("login-test", "password2");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
Time.setOffset(8000000);
resetPassword("login-test", "password3");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password3", "Invalid password: must not be equal to any of last 3 passwords.");
resetPassword("login-test", "password3");
resetPasswordInvalidPassword("login-test", "password1", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password2", "Invalid password: must not be equal to any of last 3 passwords.");
resetPasswordInvalidPassword("login-test", "password3", "Invalid password: must not be equal to any of last 3 passwords.");
resetPassword("login-test", "password");
resetPassword("login-test", "password");
} finally {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setPasswordPolicy(new PasswordPolicy(null));
}
});
Time.setOffset(0);
}
}
@Test

View file

@ -77,7 +77,7 @@ public class AccountPageTest {
accountUpdateProfilePage.openLanguage("German");
Assert.assertEquals("Deutsch", accountUpdateProfilePage.getLanguageDropdownText());
accountUpdateProfilePage.openLanguage("Englisch");
accountUpdateProfilePage.openLanguage("English");
Assert.assertEquals("English", accountUpdateProfilePage.getLanguageDropdownText());
accountUpdateProfilePage.logout();
}

View file

@ -75,7 +75,7 @@ public class LoginPageTest {
loginPage.openLanguage("German");
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
loginPage.openLanguage("Englisch");
loginPage.openLanguage("English");
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
}

View file

@ -0,0 +1,266 @@
package org.keycloak.testsuite.model;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GrantedConsentModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class GrantedConsentModelTest extends AbstractModelTest {
@Before
public void setupEnv() {
RealmModel realm = realmManager.createRealm("original");
ClientModel fooClient = realm.addClient("foo-client");
ClientModel barClient = realm.addClient("bar-client");
RoleModel realmRole = realm.addRole("realm-role");
RoleModel barClientRole = barClient.addRole("bar-client-role");
ProtocolMapperModel fooMapper = new ProtocolMapperModel();
fooMapper.setName("foo");
fooMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
fooMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID);
fooMapper = fooClient.addProtocolMapper(fooMapper);
ProtocolMapperModel barMapper = new ProtocolMapperModel();
barMapper.setName("bar");
barMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
barMapper.setProtocolMapper(UserPropertyMapper.PROVIDER_ID);
barMapper = barClient.addProtocolMapper(barMapper);
UserModel john = session.users().addUser(realm, "john");
UserModel mary = session.users().addUser(realm, "mary");
GrantedConsentModel johnFooGrant = new GrantedConsentModel(fooClient.getId());
johnFooGrant.addGrantedRole(realmRole.getId());
johnFooGrant.addGrantedRole(barClientRole.getId());
johnFooGrant.addGrantedProtocolMapper(fooMapper.getId());
john.addGrantedConsent(johnFooGrant);
GrantedConsentModel johnBarGrant = new GrantedConsentModel(barClient.getId());
johnBarGrant.addGrantedProtocolMapper(barMapper.getId());
johnBarGrant.addGrantedRole(realmRole.getId());
// Update should fail as grant doesn't yet exists
try {
john.updateGrantedConsent(johnBarGrant);
Assert.fail("Not expected to end here");
} catch (ModelException expected) {
}
john.addGrantedConsent(johnBarGrant);
GrantedConsentModel maryFooGrant = new GrantedConsentModel(fooClient.getId());
maryFooGrant.addGrantedRole(realmRole.getId());
maryFooGrant.addGrantedProtocolMapper(fooMapper.getId());
mary.addGrantedConsent(maryFooGrant);
commit();
}
@Test
public void basicConsentTest() {
RealmModel realm = realmManager.getRealm("original");
Map<String, ClientModel> clients = realm.getClientNameMap();
ClientModel fooClient = clients.get("foo-client");
ClientModel barClient = clients.get("bar-client");
UserModel john = session.users().getUserByUsername("john", realm);
UserModel mary = session.users().getUserByUsername("mary", realm);
GrantedConsentModel johnFooConsent = john.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
GrantedConsentModel johnBarConsent = john.getGrantedConsentByClient(barClient.getId());
Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent));
Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent));
GrantedConsentModel maryConsent = mary.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent));
Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent));
Assert.assertNull(mary.getGrantedConsentByClient(barClient.getId()));
}
@Test
public void getAllConsentTest() {
RealmModel realm = realmManager.getRealm("original");
Map<String, ClientModel> clients = realm.getClientNameMap();
ClientModel fooClient = clients.get("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
UserModel mary = session.users().getUserByUsername("mary", realm);
List<GrantedConsentModel> johnConsents = john.getGrantedConsents();
Assert.assertEquals(2, johnConsents.size());
List<GrantedConsentModel> maryConsents = mary.getGrantedConsents();
Assert.assertEquals(1, maryConsents.size());
GrantedConsentModel maryConsent = maryConsents.get(0);
Assert.assertEquals(maryConsent.getClientId(), fooClient.getId());
Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent));
}
@Test
public void updateWithRoleRemovalTest() {
RealmModel realm = realmManager.getRealm("original");
ClientModel fooClient = realm.getClientNameMap().get("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
GrantedConsentModel johnConsent = john.getGrantedConsentByClient(fooClient.getId());
// Remove foo protocol mapper from johnConsent
ProtocolMapperModel protMapperModel = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo");
johnConsent.getGrantedProtocolMappers().remove(protMapperModel.getId());
// Remove realm-role and add new-realm-role to johnConsent
RoleModel realmRole = realm.getRole("realm-role");
johnConsent.getGrantedRoles().remove(realmRole.getId());
RoleModel newRealmRole = realm.addRole("new-realm-role");
johnConsent.addGrantedRole(newRealmRole.getId());
john.updateGrantedConsent(johnConsent);
commit();
realm = realmManager.getRealm("original");
fooClient = realm.getClientNameMap().get("foo-client");
john = session.users().getUserByUsername("john", realm);
johnConsent = john.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
Assert.assertFalse(isRoleGranted(realm, "realm-role", johnConsent));
Assert.assertTrue(isRoleGranted(realm, "new-realm-role", johnConsent));
Assert.assertFalse(isMapperGranted(fooClient, "foo", johnConsent));
}
@Test
public void revokeTest() {
RealmModel realm = realmManager.getRealm("original");
ClientModel fooClient = realm.getClientNameMap().get("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
john.revokeGrantedConsentForClient(fooClient.getId());
commit();
realm = realmManager.getRealm("original");
john = session.users().getUserByUsername("john", realm);
Assert.assertNull(john.getGrantedConsentByClient(fooClient.getId()));
}
@Test
public void deleteUserTest() {
// Validate user deleted without any referential constraint errors
RealmModel realm = realmManager.getRealm("original");
UserModel john = session.users().getUserByUsername("john", realm);
session.users().removeUser(realm, john);
}
@Test
public void deleteProtocolMapperTest() {
RealmModel realm = realmManager.getRealm("original");
ClientModel fooClient = realm.getClientNameMap().get("foo-client");
ProtocolMapperModel fooMapper = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo");
String fooMapperId = fooMapper.getId();
fooClient.removeProtocolMapper(fooMapper);
commit();
realm = realmManager.getRealm("original");
fooClient = realm.getClientNameMap().get("foo-client");
UserModel john = session.users().getUserByUsername("john", realm);
GrantedConsentModel johnConsent = john.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0);
Assert.assertFalse(johnConsent.isProtocolMapperGranted(fooMapperId));
}
@Test
public void deleteRoleTest() {
RealmModel realm = realmManager.getRealm("original");
RoleModel realmRole = realm.getRole("realm-role");
String realmRoleId = realmRole.getId();
realm.removeRole(realmRole);
commit();
realm = realmManager.getRealm("original");
Map<String, ClientModel> clients = realm.getClientNameMap();
ClientModel fooClient = clients.get("foo-client");
ClientModel barClient = clients.get("bar-client");
UserModel john = session.users().getUserByUsername("john", realm);
GrantedConsentModel johnConsent = john.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(johnConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertFalse(johnConsent.isRoleGranted(realmRoleId));
Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnConsent));
}
@Test
public void deleteClientTest() {
RealmModel realm = realmManager.getRealm("original");
Map<String, ClientModel> clients = realm.getClientNameMap();
ClientModel barClient = clients.get("bar-client");
realm.removeClient(barClient.getId());
commit();
realm = realmManager.getRealm("original");
clients = realm.getClientNameMap();
ClientModel fooClient = clients.get("foo-client");
Assert.assertNull(clients.get("bar-client"));
UserModel john = session.users().getUserByUsername("john", realm);
GrantedConsentModel johnFooConsent = john.getGrantedConsentByClient(fooClient.getId());
Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 1);
Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1);
Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent));
Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent));
Assert.assertNull(john.getGrantedConsentByClient(barClient.getId()));
}
private boolean isRoleGranted(RoleContainerModel roleContainer, String roleName, GrantedConsentModel consentModel) {
RoleModel role = roleContainer.getRole(roleName);
return consentModel.isRoleGranted(role.getId());
}
private boolean isMapperGranted(ClientModel client, String protocolMapperName, GrantedConsentModel consentModel) {
ProtocolMapperModel protocolMapper = client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, protocolMapperName);
return consentModel.isProtocolMapperGranted(protocolMapper.getId());
}
}

View file

@ -0,0 +1,98 @@
package org.keycloak.testsuite.pages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.UriBuilder;
import org.keycloak.services.Urls;
import org.keycloak.testsuite.Constants;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AccountAccessPage extends AbstractAccountPage {
private String path = Urls.accountAccessPage(UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT).build(), "test").toString();
@Override
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/access");
}
@Override
public void open() {
driver.navigate().to(path);
}
public void setPath(String path) {
this.path = path;
}
public void revokeGrant(String clientId) {
driver.findElement(By.id("revoke-" + clientId)).click();
}
public Map<String, ClientGrant> getClientGrants() {
Map<String, ClientGrant> table = new HashMap<String, ClientGrant>();
for (WebElement r : driver.findElements(By.tagName("tr"))) {
int count = 0;
ClientGrant currentGrant = null;
for (WebElement col : r.findElements(By.tagName("td"))) {
count++;
switch (count) {
case 1:
currentGrant = new ClientGrant();
String clientId = col.getText();
table.put(clientId, currentGrant);
break;
case 2:
String protMappersStr = col.getText();
String[] protMappers = protMappersStr.split(",");
for (String protMapper : protMappers) {
protMapper = protMapper.trim();
currentGrant.addMapper(protMapper);
}
break;
case 3:
String rolesStr = col.getText();
String[] roles = rolesStr.split(",");
for (String role : roles) {
role = role.trim();
currentGrant.addRole(role);
}
break;
}
}
}
table.remove("Client");
return table;
}
public static class ClientGrant {
private final List<String> protocolMapperDescriptions = new ArrayList<String>();
private final List<String> roleDescriptions = new ArrayList<String>();
private void addMapper(String protocolMapper) {
protocolMapperDescriptions.add(protocolMapper);
}
private void addRole(String role) {
roleDescriptions.add(role);
}
public List<String> getProtocolMapperDescriptions() {
return protocolMapperDescriptions;
}
public List<String> getRoleDescriptions() {
return roleDescriptions;
}
}
}

View file

@ -32,7 +32,10 @@
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
"realmRoles": ["manager"],
"applicationRoles": {
"account": [ "manage-account" ]
}
},
{
"username" : "test-user-noemail",
@ -43,7 +46,10 @@
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
"realmRoles": ["manager"],
"applicationRoles": {
"account": [ "manage-account" ]
}
},
{
"username" : "pedroigor",
@ -53,7 +59,10 @@
{ "type" : "password",
"value" : "password" }
],
"realmRoles": ["manager"]
"realmRoles": ["manager"],
"applicationRoles": {
"account": [ "manage-account" ]
}
}
],
"roles" : {