Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Vlasta Ramik 2015-10-12 13:55:36 +02:00
commit 1d829a8365
61 changed files with 1888 additions and 232 deletions

View file

@ -231,7 +231,7 @@ public class ClientRegistration {
public ClientRegistration build() { public ClientRegistration build() {
ClientRegistration clientRegistration = new ClientRegistration(); ClientRegistration clientRegistration = new ClientRegistration();
clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration"; clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault(); clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
clientRegistration.auth = auth; clientRegistration.auth = auth;

View file

@ -47,6 +47,7 @@
<!ENTITY ProtocolMappers SYSTEM "modules/protocol-mappers.xml"> <!ENTITY ProtocolMappers SYSTEM "modules/protocol-mappers.xml">
<!ENTITY Recaptcha SYSTEM "modules/recaptcha.xml"> <!ENTITY Recaptcha SYSTEM "modules/recaptcha.xml">
<!ENTITY AuthSPI SYSTEM "modules/auth-spi.xml"> <!ENTITY AuthSPI SYSTEM "modules/auth-spi.xml">
<!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
]> ]>
<book> <book>
@ -105,6 +106,7 @@ This one is short
&TomcatAdapter; &TomcatAdapter;
&Jetty9Adapter; &Jetty9Adapter;
&Jetty8Adapter; &Jetty8Adapter;
&FilterAdapter;
&FuseAdapter; &FuseAdapter;
&JavascriptAdapter; &JavascriptAdapter;
&SpringBootAdapter; &SpringBootAdapter;

View file

@ -0,0 +1,51 @@
<section>
<title>Java Servlet Filter Adapter</title>
<para>
If you want to use Keycloak with a Java servlet application that doesn't have an adapter for that servlet
platform, you can opt to use the servlet filter adapter that Keycloak has. This adapter works a little
differently than the other adapters. You do not define security constraints in web.xml. Instead you define
a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure.
</para>
<warning>
<para>
Backchannel logout works a bit differently than the standard adapters. Instead of invalidating the http session
it instead marks the session id as logged out. There's just no way of arbitrarily invalidating an http session
based on a session id.
</para>
</warning>
<programlisting>
<![CDATA[
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<filter>
<filter-name>Keycloak Filter</filter-name>
<filter-class>org.keycloak.adapters.servlet.KeycloakOIDCFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Keycloak Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
]]>
</programlisting>
<para>
The Keycloak filter has the same configuration parameters available as the other adapters except you must define
them as filter init params instead of context params.
</para>
<para>
To use this filter, include this maven artifact in your WAR poms
</para>
<programlisting><![CDATA[
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<version>&project.version;</version>
</dependency>
]]></programlisting>
</section>

View file

@ -7,6 +7,7 @@
<!ENTITY TomcatAdapter SYSTEM "modules/tomcat-adapter.xml"> <!ENTITY TomcatAdapter SYSTEM "modules/tomcat-adapter.xml">
<!ENTITY Jetty9Adapter SYSTEM "modules/jetty9-adapter.xml"> <!ENTITY Jetty9Adapter SYSTEM "modules/jetty9-adapter.xml">
<!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml"> <!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
<!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
<!ENTITY Logout SYSTEM "modules/logout.xml"> <!ENTITY Logout SYSTEM "modules/logout.xml">
]> ]>
@ -46,6 +47,7 @@ This one is short
&TomcatAdapter; &TomcatAdapter;
&Jetty9Adapter; &Jetty9Adapter;
&Jetty8Adapter; &Jetty8Adapter;
&FilterAdapter;
&Logout; &Logout;

View file

@ -59,13 +59,13 @@
<para> <para>
Here is the explanation of the SP element attributes Here is the explanation of the SP element attributes
</para> </para>
<para><![CDATA[ <programlisting><![CDATA[
<SP entityID="sp" <SP entityID="sp"
sslPolicy="ssl" sslPolicy="ssl"
nameIDPolicyFormat="format" nameIDPolicyFormat="format"
forceAuthentication="true"> forceAuthentication="true">
... ...
</SP>]]></para> </SP>]]></programlisting>
<para> <para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
@ -129,7 +129,7 @@
or you can cut and paste the keys directly within <literal>keycloak-saml.xml</literal> or you can cut and paste the keys directly within <literal>keycloak-saml.xml</literal>
in the PEM format. in the PEM format.
</para> </para>
<para><![CDATA[ <programlisting><![CDATA[
<Keys> <Keys>
<Key signing="true" > <Key signing="true" >
<KeyStore resource="/WEB-INF/keystore.jks" password="store123"> <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
@ -139,7 +139,7 @@
</Key> </Key>
</Keys> </Keys>
]]> ]]>
</para> </programlisting>
<para> <para>
The <literal>Key</literal> element has two optional attributes <literal>signing</literal> The <literal>Key</literal> element has two optional attributes <literal>signing</literal>
and <literal>encryption</literal>. When set to true these tell the adapter what the and <literal>encryption</literal>. When set to true these tell the adapter what the
@ -215,13 +215,13 @@
</section> </section>
<section> <section>
<title>RoleIdentifiers element</title> <title>RoleIdentifiers element</title>
<para><![CDATA[ <programlisting><![CDATA[
<RoleIdentifiers> <RoleIdentifiers>
<Attribute name="Role"/> <Attribute name="Role"/>
<Attribute name="member"/> <Attribute name="member"/>
<Attribute name="memberOf"/> <Attribute name="memberOf"/>
</RoleIdentifiers> </RoleIdentifiers>
]]></para> ]]></programlisting>
<para> <para>
This element is optional. It defines which SAML attribute values in the assertion should be This element is optional. It defines which SAML attribute values in the assertion should be
mapped to a Java EE role. By default <literal>Role</literal> attribute values are converted mapped to a Java EE role. By default <literal>Role</literal> attribute values are converted
@ -236,7 +236,7 @@
Everything in the IDP element describes the settings for the IDP the SP is communicating Everything in the IDP element describes the settings for the IDP the SP is communicating
with. with.
</para> </para>
<para> <programlisting>
<![CDATA[ <![CDATA[
<IDP entityID="idp" <IDP entityID="idp"
signaturesRequired="true" signaturesRequired="true"
@ -244,7 +244,7 @@
signatureCanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#"> signatureCanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#">
... ...
</IDP>]]> </IDP>]]>
</para> </programlisting>
<para> <para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
@ -300,12 +300,12 @@
The <literal>SignleSignOnService</literal> sub element defines the The <literal>SignleSignOnService</literal> sub element defines the
login SAML endpoint of the IDP. login SAML endpoint of the IDP.
</para> </para>
<para><![CDATA[ <programlisting><![CDATA[
<SingleSignOnService signRequest="true" <SingleSignOnService signRequest="true"
validateResponseSignature="true" validateResponseSignature="true"
requestBinding="post" requestBinding="post"
bindingUrl="url"/> bindingUrl="url"/>
]]></para> ]]></programlisting>
<para> <para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
@ -367,7 +367,7 @@
The <literal>SignleSignOnService</literal> sub element defines the The <literal>SignleSignOnService</literal> sub element defines the
login SAML endpoint of the IDP. login SAML endpoint of the IDP.
</para> </para>
<para><![CDATA[ <programlisting><![CDATA[
<SingleLogoutService validateRequestSignature="true" <SingleLogoutService validateRequestSignature="true"
validateResponseSignature="true" validateResponseSignature="true"
signRequest="true" signRequest="true"
@ -376,7 +376,7 @@
responseBinding="post" responseBinding="post"
postBindingUrl="posturl" postBindingUrl="posturl"
redirectBindingUrl="redirecturl"> redirectBindingUrl="redirecturl">
]]></para> ]]></programlisting>
<para> <para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>

View file

@ -0,0 +1,55 @@
<chapter>
<title>Java Servlet Filter Adapter</title>
<para>
If you want to use SAML with a Java servlet application that doesn't have an adapter for that servlet
platform, you can opt to use the servlet filter adapter that Keycloak has. This adapter works a little
differently than the other adapters. You do not define security constraints in web.xml. Instead you define
a filter mapping using the Keycloak servlet filter adapter to secure the url patterns you want to secure.
</para>
<warning>
<para>
Backchannel logout works a bit differently than the standard adapters. Instead of invalidating the http session
it instead marks the session id as logged out. There's just no way of arbitrarily invalidating an http session
based on a session id.
</para>
</warning>
<warning>
<para>
Backchannel logout does not currently work when you have a clustered application that uses the SAML filter.
</para>
</warning>
<programlisting>
<![CDATA[
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>customer-portal</module-name>
<filter>
<filter-name>Keycloak Filter</filter-name>
<filter-class>org.keycloak.adapters.saml.servlet.SamlFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Keycloak Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
]]>
</programlisting>
<para>
The Keycloak filter has the same configuration parameters available as the other adapters except you must define
them as filter init params instead of context params.
</para>
<para>
To use this filter, include this maven artifact in your WAR poms
</para>
<programlisting><![CDATA[
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<version>&project.version;</version>
</dependency>
]]></programlisting>
</chapter>

View file

@ -126,3 +126,4 @@ locale_de=Deutsch
locale_en=English locale_en=English
locale_it=Italian locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français

View file

@ -150,3 +150,4 @@ locale_de=German
locale_en=English locale_en=English
locale_it=Italian locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français

View file

@ -0,0 +1,156 @@
# TIPS to encode UTF-8 to ISO
# native2ascii -encoding ISO8859_1 srcFile > dstFile
doSave=Sauvegarder
doCancel=Annuler
doLogOutAllSessions=D\u00e9connexion de toutes les sessions
doRemove=Supprimer
doAdd=Ajouter
doSignOut=D\u00e9connexion
editAccountHtmlTtile=Edition du compte
federatedIdentitiesHtmlTitle=Identit\u00e9s f\u00e9d\u00e9r\u00e9es
accountLogHtmlTitle=Acces au compte
changePasswordHtmlTitle=Changer de mot de passe
sessionsHtmlTitle=Sessions
accountManagementTitle=Gestion de Compte Keycloak
authenticatorTitle=Authentification
applicationsHtmlTitle=Applications
authenticatorCode=Mot de passe unique
email=Courriel
firstName=Nom
givenName=Pr\u00e9nom
fullName=Nom Complet
lastName=Last name
familyName=Nom de Famille
password=Mot de passe
passwordConfirm=Confirmation
passwordNew=Nouveau mot de passe
username=Compte
address=Adresse
street=Rue
locality=Ville ou Localit\u00e9
region=State, Province, or R\u00e9gion
postal_code=Code Postal
country=Pays
emailVerified=Courriel v\u00e9rifi\u00e9
gssDelegationCredential=Accr\u00e9ditation de d\u00e9l\u00e9gation GSS
role_admin=Administrateur
role_realm-admin=Administrateur du domaine
role_create-realm=Cr\u00e9er un domaine
role_view-realm=Voir un domaine
role_view-users=Voir les utilisateurs
role_view-applications=Voir les applications
role_view-clients=Voir les clients
role_view-events=Voir les \u00e9v\u00e9nements
role_view-identity-providers=Voir les fournisseurs d'identit\u00e9s
role_manage-realm=G\u00e9rer le domaine
role_manage-users=G\u00e9rer les utilisateurs
role_manage-applications=G\u00e9rer les applications
role_manage-identity-providers=G\u00e9rer les fournisseurs d'identit\u00e9s
role_manage-clients=G\u00e9rer les clients
role_manage-events=G\u00e9rer les \u00e9v\u00e9nements
role_view-profile=Voir le profile
role_manage-account=G\u00e9rer le compte
role_read-token=Lire le jeton d'authentification
role_offline-access=Acc\u00e9s hors-ligne
client_account=Compte
client_security-admin-console=Console d'administration de la s\u00e9curit\u00e9
client_realm-management=Gestion du domaine
client_broker=Broker
requiredFields=Champs obligatoires
allFieldsRequired=Tous les champs obligatoires
backToApplication=&laquo; Revenir \u00e0 l'application
backTo=Revenir \u00e0 {0}
date=Date
event=Ev\u00e9nement
ip=IP
client=Client
clients=Clients
details=D\u00e9tails
started=S\u00e9lectionn\u00e9
lastAccess=Dernier acc\u00e8s
expires=Expires
applications=Applications
account=Compte
federatedIdentity=Identit\u00e9 f\u00e9d\u00e9r\u00e9e
authenticator=Authentification
sessions=Sessions
log=Connexion
application=Application
availablePermissions=Permissions Disponibles
grantedPermissions=Permissions accord\u00e9es
grantedPersonalInfo=Informations personnels accord\u00e9es
additionalGrants=Droits additionnels
action=Action
inResource=dans
fullAccess=Acc\u00e9s complet
offlineToken=Jeton d'authentification hors-ligne
revoke=R\u00e9voquer un droit
configureAuthenticators=Authentifications configur\u00e9es.
mobile=T\u00e9l\u00e9phone mobile
totpStep1=Installlez <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> ou bien Google Authenticator sur votre mobile. Ces deux applications sont disponibles sur <a href="https://play.google.com">Google Play</a> et Apple App Store.
totpStep2=Ouvrez l'application et scanner le code bar ou entrez la clef.
totpStep3=Entrez le code \u00e0 usage unique fourni par l'application et cliquez sur Sauvegarder pour terminer.
missingUsernameMessage=Veuillez entrez votre nom d'utilisateur.
missingFirstNameMessage=Veuillez entrez votre pr\u00e9nom.
invalidEmailMessage=Courriel invalide.
missingLastNameMessage=Veuillez entrez votre nom.
missingEmailMessage=Veuillez entrez votre courriel.
missingPasswordMessage=Veuillez entrez votre mot de passe.
notMatchPasswordMessage=Les mots de passe ne sont pas identiques
missingTotpMessage=Veuillez entrez le code authentification.
invalidPasswordExistingMessage=Mot de passe existant invalide.
invalidPasswordConfirmMessage=Le mot de passe de confirmation ne correspond pas.
invalidTotpMessage=Le code d'authentification est invalide.
usernameExistsMessage=Le nom d'utilisateur existe d\u00e9j\u00e0.
emailExistsMessage=Le courriel existe d\u00e9j\u00e0.
readOnlyUserMessage=Vous ne pouvez pas mettre \u00e0 jour votre compte car il est en lecture seule.
readOnlyPasswordMessage=Vous ne pouvez pas mettre \u00e0 jour votre mot de passe car votre compte est en lecture seule.
successTotpMessage=L'authentification via t\u00e9l\u00e9phone mobile est configur\u00e9e.
successTotpRemovedMessage=L'authentification via t\u00e9l\u00e9phone mobile est supprim\u00e9e.
successGrantRevokedMessage=Droit r\u00e9voqu\u00e9 avec succ\u00e8s.
accountUpdatedMessage=Votre compte a \u00e9t\u00e9 mis \u00e0 jour.
accountPasswordUpdatedMessage=Votre mot de passe a \u00e9t\u00e9 mis \u00e0 jour.
missingIdentityProviderMessage=Le fournisseur d'identit\u00e9 n'est pas sp\u00e9cifi\u00e9.
invalidFederatedIdentityActionMessage=Action manquante ou invalide.
identityProviderNotFoundMessage=Le fournisseur d'identit\u00e9 sp\u00e9cifi\u00e9 n'est pas trouv\u00e9.
federatedIdentityLinkNotActiveMessage=Cette identit\u00e9 n'est plus active dor\u00e9navant.
federatedIdentityRemovingLastProviderMessage=Vous ne pouvez pas supprimer votre derni\u00e8re f\u00e9d\u00e9ration d'identit\u00e9 sans avoir de mot de passe sp\u00e9cifi\u00e9.
identityProviderRedirectErrorMessage=Erreur de redirection vers le fournisseur d'identit\u00e9.
identityProviderRemovedMessage=Le fournisseur d'identit\u00e9 a \u00e9t\u00e9 supprim\u00e9 correctement.
accountDisabledMessage=Ce compte est d\u00e9sactiv\u00e9, veuillez contacter votre administrateur.
accountTemporarilyDisabledMessage=Ce compte est temporairement d\u00e9sactiv\u00e9, veuillez contacter votre administrateur ou r\u00e9essayez plus tard..
invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caractère(s) spéciaux.
invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas être identique au nom d'utilisateur.
invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l'expression rationnelle.
invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas être égal aux {0} derniers mot de passe.
locale_de=German
locale_en=English
locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Fran\u00e7ais

View file

@ -125,3 +125,4 @@ locale_de=German
locale_en=English locale_en=English
locale_it=Italian locale_it=Italian
locale_pt-BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil)
locale_fr=Français

View file

@ -147,3 +147,4 @@ locale_de=Deutsch
locale_en=English locale_en=English
locale_it=Italian locale_it=Italian
locale_pt-BR=Portugu\u00EAs (BR) locale_pt-BR=Portugu\u00EAs (BR)
locale_fr=Français

View file

@ -0,0 +1,116 @@
# Common messages
enabled=Actif
name=Nom
save=Sauver
cancel=Annuler
onText=Oui
offText=Non
client=Client
clear=Effacer
# Realm settings
realm-detail.enabled.tooltip=Les utilisateurs et les clients peuvent acc\u00e9der au domaine si celui-ci est actif
registrationAllowed=Enregistrement d''utilisateur
registrationAllowed.tooltip=Activ\u00e9/d\u00e9sactiv\u00e9 la page d''enregistrement. Un lien pour l''enregistrement sera visible sur la page de connexion.
registrationEmailAsUsername=Courriel comme nom d''utilisateur
registrationEmailAsUsername.tooltip=Si actif le champ du nom de l''utilisateur est cach\u00e9 pendant l''enregistrement, le courriel est utilis\u00e9 comme nom d''utilisateur.
editUsernameAllowed=Editez le nom de l''utilisateur
editUsernameAllowed.tooltip=Si actif, le champ du nom de l''utilisateur est modifiable.
resetPasswordAllowed=Mot de passe oubli\u00e9
resetPasswordAllowed.tooltip=Afficher un lien sur la page de connexion pour les utilisateurs ayant oubli\u00e9s leurs accr\u00e9ditations.
rememberMe=Se souvenir de moi
rememberMe.tooltip=Afficher la case \u00e0 cocher sur la page de connexion pour permettre aux utilisateurs de rester connecter entre deux red\u00e9marrages de leur navigateur jusqu''\u00e0 expiration de la session.
verifyEmail=Verifier le courriel
verifyEmail.tooltip=Force l''utilisateur \u00e0 v\u00e9rifier son courriel lors de la premi\u00e8re connexion.
sslRequired=SSL requis
sslRequired.option.all=toutes les requ\u00eates
sslRequired.option.external=les requ\u00eates externes
sslRequired.option.none=aucun
sslRequired.tooltip=Si le HTTPS est requis ? ''aucun'' signifie que le HTTPS n''est requis pour aucune adresse IP cliente. ''les requ\u00eates externes'' signifie que localhost et les adresses IP priv\u00e9es peuvent acc\u00e9der sans HTTPS. ''toutes les requ\u00eates'' signifie que le protocole HTTPS est obligatoire pour toutes les adresses IP.
publicKey=Clef publique key
gen-new-keys=Cr\u00e9ation de nouvelle clef
certificate=Certificat
host=H\u00f4te
smtp-host=SMTP h\u00f4te
port=Port
smtp-port=SMTP Port (par defaut 25)
from=De
sender-email-addr=Courriel de l''exp\u00e9diteur
enable-ssl=Activer SSL/TLS
enable-start-tls=Activer StartTLS
enable-auth=Activer l''authentification
username=Nom de l''utilisateur
login-username=Connexion de l''utilisateur
password=Mot de passe
login-password=Mot de passe
login-theme=Th\u00e8me de connexion
select-one=S\u00e9lectionnez-en un...
login-theme.tooltip=S\u00e9lectionnez le th\u00e8me pour les pages de connexion, de mot de passe \u00e0 usage unique bas\u00e9 sur le temps, des droits, de l''enregistrement, et du mot passe oubli\u00e9.
account-theme=Th\u00e8me du compte
account-theme.tooltip=S\u00e9lectionnez le th\u00e8me pour la gestion des comptes.
admin-console-theme=Th\u00e8me de la console d''administration
select-theme-admin-console=S\u00e9lectionnez le theme e la console d''administration.
email-theme=Th\u00e8me pour le courriel
select-theme-email=S\u00e9lectionnez le th\u00e8me les courriels envoy\u00e9es par le serveur.
i18n-enabled=Internationalisation activ\u00e9
supported-locales=Locales support\u00e9es
supported-locales.placeholder=Entrer la locale et valider
default-locale=Locale par d\u00e9faut
realm-cache-enabled=Cache du domaine activ\u00e9
realm-cache-enabled.tooltip=Activer/D\u00e9sactiver le cache pour le domaine, client et donn\u00e9es.
user-cache-enabled=Cache utilisateur activ\u00e9
user-cache-enabled.tooltip=Activer/D\u00e9sactiver le cache utilisateur, et le cache de relation entre utilisateur et roles.
sso-session-idle=Sessions SSO inactives
seconds=Secondes
minutes=Minutes
hours=Heures
days=Jours
sso-session-max=Maximum de sessions SSO
sso-session-idle.tooltip=Temps d''inactivit\u00e9 autoris\u00e9 avant expiration de la session. Les jetons et les sessions navigateurs sont invalid\u00e9es quand la session expire.
sso-session-max.tooltip=Dur\u00e9e maximale avant que la session n''expire. Les jetons et les sessions navigateurs sont invalid\u00e9es quand la session expire.
access-token-lifespan=Dur\u00e9e de vie du jeton d''acc\u00e8s
access-token-lifespan.tooltip=Dur\u00e9e maximale avant que le jeton d''acc\u00e8s n''expire. Cette valeur devrait etre relativement plus petite que la dur\u00e9e d''inactivit\u00e9 (timeout) du SSO .
client-login-timeout=Dur\u00e9e d''inactivit\u00e9 de connexion (timeout)
client-login-timeout.tooltip=Dur\u00e9e maximale qu''a un client pour finir le protocole du jeton d''acc\u00e8s. Devrait etre de l''ordre de la minute (1 min).
login-timeout=Dur\u00e9e d''inactivit\u00e9 de connexion
login-timeout.tooltip=Dur\u00e9e maximale autoris\u00e9e pour finaliser la connexion. Devrait \u00eatre relativement long une 30 minutes voire plus.
login-action-timeout=Dur\u00e9e d''inactivit\u00e9 des actions de connexions
login-action-timeout.tooltip=Dur\u00e9e maximale qu''a un utilisateur pour finir ses actions concernants la mise \u00e0 jour de son mot de passe ou bien de la configuration du mot de passe \u00e0 usage unique (totp). Devrait \u00eatre relativement long 5 minutes voire plus.
headers=En-t\u00eates
brute-force-detection=D\u00e9tection des attaques par force brute
x-frame-options=X-Frame-Options
click-label-for-info=Cliquer sur le label pour plus d''information. Les valeurs par d\u00e9faut \u00e9vitent que les pages soient incluses dans des iframes \u00e9trang\u00e8res.
content-sec-policy=Content-Security-Policy
max-login-failures=Nombre maximale d''erreur de connexion
max-login-failures.tooltip=Combien d''erreur avant le temps d''attente.
wait-increment=Temps d''attente
wait-increment.tooltip=Quand le seuil des erreurs est atteint,combien de temps l''utilisateur est-il bloqu\u00e9 ?
quick-login-check-millis=Nombre de milli-secondes entre deux connexions
quick-login-check-millis.tooltip=Si une erreur apparait trop rapidement, bloquer le compte utilisateur.
min-quick-login-wait=Dur\u00e9e minimale d''attente entre deux connexions
min-quick-login-wait.tooltip=Dur\u00e9e d''attente demand\u00e9e apr\u00e8s une erreur entre deux connexion.
max-wait=Dur\u00e9e maximale d''attente
max-wait.tooltip=Dur\u00e9e maximale de blocage du compte utilisateur
failure-reset-time=Dur\u00e9e de remise \u00e0 z\u00e9ro des erreurs
failure-reset-time.tooltip=Quand les erreurs sont-elles remises \u00e0 z\u00e9ro ?
realm-tab-login=Connexion
realm-tab-keys=Clefs
realm-tab-email=Courriels
realm-tab-themes=Th\u00e8mes
realm-tab-cache=Cache
realm-tab-tokens=Jetons
realm-tab-security-defenses=Security Defenses
realm-tab-general=G\u00e9n\u00e9ral
add-realm=Ajouter un domaine
#Session settings
realm-sessions=Sessions du domaine
revocation=R\u00e9vocation
logout-all=D\u00e9connexion globale
active-sessions=Sessions actives
sessions=Sessions
not-before=Pas avant
not-before.tooltip=R\u00e9voquer tous les jetons demand\u00e9s avant cette date.
set-to-now=Mettre \u00e0 maintenant
push=Appuyer
push.tooltip=Pour tout les clients ayant une URL d''administration, les notifier de la politique de r\u00e9vocation.

View file

@ -0,0 +1,8 @@
invalidPasswordMinLengthMessage=Mot de passe invalide: longueur minimale {0}.
invalidPasswordMinLowerCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en minuscule.
invalidPasswordMinDigitsMessage=Mot de passe invalide: doit contenir au moins {0} chiffre(s).
invalidPasswordMinUpperCaseCharsMessage=Mot de passe invalide: doit contenir au moins {0} lettre(s) en majuscule.
invalidPasswordMinSpecialCharsMessage=Mot de passe invalide: doit contenir au moins {0} caract\u00e8re(s) sp\u00e9ciaux.
invalidPasswordNotUsernameMessage=Mot de passe invalide: ne doit pas \u00eatre identique au nom d''utilisateur.
invalidPasswordRegexPatternMessage=Mot de passe invalide: ne valide pas l''expression rationnelle.
invalidPasswordHistoryMessage=Mot de passe invalide: ne doit pas \u00eatre \u00e9gal aux {0} derniers mot de passe.

View file

@ -14,16 +14,57 @@ var loadingTimer = -1;
angular.element(document).ready(function () { angular.element(document).ready(function () {
var keycloakAuth = new Keycloak(configUrl); var keycloakAuth = new Keycloak(configUrl);
function whoAmI(success, error) {
var req = new XMLHttpRequest();
req.open('GET', consoleBaseUrl + "/whoami", true);
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Authorization', 'bearer ' + keycloakAuth.token);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
var data = JSON.parse(req.responseText);
success(data);
} else {
error();
}
}
}
req.send();
}
function hasAnyAccess(user) {
return user && user['realm_access'];
}
keycloakAuth.onAuthLogout = function() { keycloakAuth.onAuthLogout = function() {
location.reload(); location.reload();
} }
keycloakAuth.init({ onLoad: 'login-required' }).success(function () { keycloakAuth.init({ onLoad: 'login-required' }).success(function () {
auth.authz = keycloakAuth; auth.authz = keycloakAuth;
module.factory('Auth', function() {
return auth; auth.refreshPermissions = function(success, error) {
whoAmI(function(data) {
auth.user = data;
auth.loggedIn = true;
auth.hasAnyAccess = hasAnyAccess(data);
success();
}, function() {
error();
});
};
auth.refreshPermissions(function() {
module.factory('Auth', function() {
return auth;
});
angular.bootstrap(document, ["keycloak"]);
}, function() {
window.location.reload();
}); });
angular.bootstrap(document, ["keycloak"]);
}).error(function () { }).error(function () {
window.location.reload(); window.location.reload();
}); });

View file

@ -1,31 +1,9 @@
module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $location, Notifications, ServerInfo) { module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo) {
$scope.addMessage = function() {
Notifications.success("test");
};
$scope.authUrl = authUrl; $scope.authUrl = authUrl;
$scope.resourceUrl = resourceUrl; $scope.resourceUrl = resourceUrl;
$scope.auth = Auth; $scope.auth = Auth;
$scope.serverInfo = ServerInfo.get(); $scope.serverInfo = ServerInfo.get();
function hasAnyAccess() {
var realmAccess = Auth.user && Auth.user['realm_access'];
if (realmAccess) {
for (var p in realmAccess){
return true;
}
return false;
} else {
return false;
}
}
WhoAmI.get(function (data) {
Auth.user = data;
Auth.loggedIn = true;
Auth.hasAnyAccess = hasAnyAccess();
});
function getAccess(role) { function getAccess(role) {
if (!Current.realm) { if (!Current.realm) {
return false; return false;
@ -155,7 +133,7 @@ module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $l
} }
}); });
module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, WhoAmI, $location, $route, Dialog, Notifications, Auth, $modal) { module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, $location, $route, Dialog, Notifications, Auth, $modal) {
console.log('RealmCreateCtrl'); console.log('RealmCreateCtrl');
Current.realm = null; Current.realm = null;
@ -193,22 +171,17 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
}, true); }, true);
$scope.$watch('realm.realm', function() { $scope.$watch('realm.realm', function() {
if (create) {
$scope.realm.id = $scope.realm.realm; $scope.realm.id = $scope.realm.realm;
}
}, true); }, true);
$scope.save = function() { $scope.save = function() {
var realmCopy = angular.copy($scope.realm); var realmCopy = angular.copy($scope.realm);
Realm.create(realmCopy, function() { Realm.create(realmCopy, function() {
Realm.query(function(data) { Notifications.success("The realm has been created.");
Current.realms = data;
WhoAmI.get(function(user) {
Auth.user = user;
Auth.refreshPermissions(function() {
$scope.$apply(function() {
$location.url("/realms/" + realmCopy.realm); $location.url("/realms/" + realmCopy.realm);
Notifications.success("The realm has been created.");
}); });
}); });
}); });
@ -227,7 +200,7 @@ module.controller('ObjectModalCtrl', function($scope, object) {
$scope.object = object; $scope.object = object;
}); });
module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, WhoAmI, Auth) { module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, Auth) {
$scope.createRealm = !realm.realm; $scope.createRealm = !realm.realm;
$scope.serverInfo = serverInfo; $scope.serverInfo = serverInfo;
@ -272,11 +245,13 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
}); });
if (nameChanged) { if (nameChanged) {
WhoAmI.get(function(user) { Auth.refreshPermissions(function() {
Auth.user = user; Auth.refreshPermissions(function() {
Notifications.success("Your changes have been saved to the realm.");
$location.url("/realms/" + realmCopy.realm); $scope.$apply(function() {
Notifications.success("Your changes have been saved to the realm."); $location.url("/realms/" + realmCopy.realm);
});
});
}); });
} else { } else {
$location.url("/realms/" + realmCopy.realm); $location.url("/realms/" + realmCopy.realm);
@ -1770,6 +1745,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
execution.postLevels.push(j); execution.postLevels.push(j);
} }
} }
$location.url("/realms/" + realm.realm + "/authentication/flows/" + $scope.flow.alias);
}) })
}; };

View file

@ -184,10 +184,6 @@ module.factory('Notifications', function($rootScope, $timeout) {
return notifications; return notifications;
}); });
module.factory('WhoAmI', function($resource) {
return $resource(consoleBaseUrl + '/whoami');
});
module.factory('Realm', function($resource) { module.factory('Realm', function($resource) {
return $resource(authUrl + '/admin/realms/:id', { return $resource(authUrl + '/admin/realms/:id', {
id : '@realm' id : '@realm'

View file

@ -2,6 +2,6 @@
<div class="error-container"> <div class="error-container">
<h2>Resource <strong>not found</strong>...</h2> <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> <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> <a href="#/" class="link-right">Go to the home page &raquo;</a>
</div> </div>
</div> </div>

View file

@ -11,7 +11,7 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<tr> <tr>
<td width="20%">Keycloak Version</td> <td width="20%">Server Version</td>
<td>{{serverInfo.systemInfo.version}}</td> <td>{{serverInfo.systemInfo.version}}</td>
</tr> </tr>
<tr> <tr>

View file

@ -114,6 +114,11 @@ public class BearerTokenRequestAuthenticator {
return false; return false;
} }
@Override
public int getResponseCode() {
return 0;
}
@Override @Override
public boolean challenge(HttpFacade exchange) { public boolean challenge(HttpFacade exchange) {
// do the same thing as client cert auth // do the same thing as client cert auth
@ -139,6 +144,11 @@ public class BearerTokenRequestAuthenticator {
return true; return true;
} }
@Override
public int getResponseCode() {
return 401;
}
@Override @Override
public boolean challenge(HttpFacade facade) { public boolean challenge(HttpFacade facade) {
facade.getResponse().setStatus(401); facade.getResponse().setStatus(401);

View file

@ -181,6 +181,11 @@ public class OAuthRequestAuthenticator {
public boolean errorPage() { public boolean errorPage() {
return true; return true;
} }
@Override
public int getResponseCode() {
return 403;
}
}; };
} }
return new AuthChallenge() { return new AuthChallenge() {
@ -190,6 +195,11 @@ public class OAuthRequestAuthenticator {
return false; return false;
} }
@Override
public int getResponseCode() {
return 0;
}
@Override @Override
public boolean challenge(HttpFacade exchange) { public boolean challenge(HttpFacade exchange) {
tokenStore.saveRequest(); tokenStore.saveRequest();
@ -262,6 +272,11 @@ public class OAuthRequestAuthenticator {
return true; return true;
} }
@Override
public int getResponseCode() {
return code;
}
@Override @Override
public boolean challenge(HttpFacade exchange) { public boolean challenge(HttpFacade exchange) {
exchange.getResponse().setStatus(code); exchange.getResponse().setStatus(code);

View file

@ -13,9 +13,17 @@ public interface AuthChallenge {
boolean challenge(HttpFacade exchange); boolean challenge(HttpFacade exchange);
/** /**
* Whether or not an error page should be displayed if possible * Whether or not an error page should be displayed if possible along with the challenge
* *
* @return * @return
*/ */
boolean errorPage(); boolean errorPage();
/**
* If errorPage is true, this is the response code the challenge will send. This is used by platforms
* that call HttpServletResponse.sendError() to forward to error page.
*
* @return
*/
int getResponseCode();
} }

View file

@ -22,6 +22,14 @@ public class InMemorySessionIdMapper implements SessionIdMapper {
return sessionToSso.containsKey(id) || sessionToPrincipal.containsKey(id); return sessionToSso.containsKey(id) || sessionToPrincipal.containsKey(id);
} }
@Override
public void clear() {
ssoToSession.clear();
sessionToSso.clear();
principalToSession.clear();
sessionToPrincipal.clear();
}
@Override @Override
public Set<String> getUserSessions(String principal) { public Set<String> getUserSessions(String principal) {
Set<String> lookup = principalToSession.get(principal); Set<String> lookup = principalToSession.get(principal);

View file

@ -9,6 +9,8 @@ import java.util.Set;
public interface SessionIdMapper { public interface SessionIdMapper {
boolean hasSession(String id); boolean hasSession(String id);
void clear();
Set<String> getUserSessions(String principal); Set<String> getUserSessions(String principal);
String getSessionFromSSO(String sso); String getSessionFromSSO(String sso);

View file

@ -262,16 +262,16 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
} }
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
challenge.challenge(facade);
if (challenge.errorPage() && errorPage != null) { if (challenge.errorPage() && errorPage != null) {
Response response = (Response)res; Response response = (Response)res;
try { try {
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage))); response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), errorPage)));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
challenge.challenge(facade);
} }
return Authentication.SEND_CONTINUE; return Authentication.SEND_CONTINUE;
} }

View file

@ -26,6 +26,7 @@
<module>undertow-adapter-spi</module> <module>undertow-adapter-spi</module>
<module>undertow</module> <module>undertow</module>
<module>wildfly</module> <module>wildfly</module>
<module>servlet-filter</module>
<module>js</module> <module>js</module>
<module>installed</module> <module>installed</module>
<module>admin-client</module> <module>admin-client</module>

View file

@ -2,14 +2,32 @@ package org.keycloak.adapters.servlet;
import org.keycloak.adapters.AdapterSessionStore; import org.keycloak.adapters.AdapterSessionStore;
import org.keycloak.adapters.HttpFacade; import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.util.Encode;
import org.keycloak.util.MultivaluedHashMap; import org.keycloak.util.MultivaluedHashMap;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -24,6 +42,7 @@ public class FilterSessionStore implements AdapterSessionStore {
protected final HttpFacade facade; protected final HttpFacade facade;
protected final int maxBuffer; protected final int maxBuffer;
protected byte[] restoredBuffer = null; protected byte[] restoredBuffer = null;
protected boolean needRequestRestore;
public FilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer) { public FilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer) {
this.request = request; this.request = request;
@ -38,6 +57,249 @@ public class FilterSessionStore implements AdapterSessionStore {
session.removeAttribute(SAVED_BODY); session.removeAttribute(SAVED_BODY);
} }
public void servletRequestLogout() {
}
public static String getCharsetFromContentType(String contentType) {
if (contentType == null)
return (null);
int start = contentType.indexOf("charset=");
if (start < 0)
return (null);
String encoding = contentType.substring(start + 8);
int end = encoding.indexOf(';');
if (end >= 0)
encoding = encoding.substring(0, end);
encoding = encoding.trim();
if ((encoding.length() > 2) && (encoding.startsWith("\""))
&& (encoding.endsWith("\"")))
encoding = encoding.substring(1, encoding.length() - 1);
return (encoding.trim());
}
public HttpServletRequestWrapper buildWrapper(HttpSession session, final KeycloakAccount account) {
if (needRequestRestore) {
final String method = (String)session.getAttribute(SAVED_METHOD);
final byte[] body = (byte[])session.getAttribute(SAVED_BODY);
final MultivaluedHashMap<String, String> headers = (MultivaluedHashMap<String, String>)session.getAttribute(SAVED_HEADERS);
clearSavedRequest(session);
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
protected MultivaluedHashMap<String, String> parameters;
MultivaluedHashMap<String, String> getParams() {
if (parameters != null) return parameters;
String contentType = getContentType();
contentType = contentType.toLowerCase();
if (contentType.startsWith("application/x-www-form-urlencoded")) {
ByteArrayInputStream is = new ByteArrayInputStream(body);
try {
parameters = parseForm(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return parameters;
}
@Override
public boolean isUserInRole(String role) {
return account.getRoles().contains(role);
}
@Override
public Principal getUserPrincipal() {
return account.getPrincipal();
}
@Override
public String getMethod() {
if (needRequestRestore) {
return method;
} else {
return super.getMethod();
}
}
@Override
public String getHeader(String name) {
if (needRequestRestore && headers != null) {
return headers.getFirst(name.toLowerCase());
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
if (needRequestRestore && headers != null) {
List<String> values = headers.getList(name.toLowerCase());
if (values == null) return Collections.emptyEnumeration();
else return Collections.enumeration(values);
}
return super.getHeaders(name);
}
@Override
public Enumeration<String> getHeaderNames() {
if (needRequestRestore && headers != null) {
return Collections.enumeration(headers.keySet());
}
return super.getHeaderNames();
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (needRequestRestore && body != null) {
final ByteArrayInputStream is = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return is.read();
}
};
}
return super.getInputStream();
}
@Override
public void logout() throws ServletException {
servletRequestLogout();
}
@Override
public long getDateHeader(String name) {
if (!needRequestRestore) return super.getDateHeader(name);
throw new RuntimeException("This method is not supported in a restored authenticated request");
}
@Override
public int getIntHeader(String name) {
if (!needRequestRestore) return super.getIntHeader(name);
String value = getHeader(name);
if (value == null) return -1;
return Integer.valueOf(value);
}
@Override
public String[] getParameterValues(String name) {
if (!needRequestRestore) return super.getParameterValues(name);
MultivaluedHashMap<String, String> formParams = getParams();
if (formParams == null) {
return super.getParameterValues(name);
}
String[] values = request.getParameterValues(name);
List<String> list = new LinkedList<>();
if (values != null) {
for (String val : values) list.add(val);
}
List<String> vals = formParams.get(name);
if (vals != null) list.addAll(vals);
return list.toArray(new String[list.size()]);
}
@Override
public Enumeration<String> getParameterNames() {
if (!needRequestRestore) return super.getParameterNames();
MultivaluedHashMap<String, String> formParams = getParams();
if (formParams == null) {
return super.getParameterNames();
}
Set<String> names = new HashSet<>();
Enumeration<String> qnames = super.getParameterNames();
while (qnames.hasMoreElements()) names.add(qnames.nextElement());
names.addAll(formParams.keySet());
return Collections.enumeration(names);
}
@Override
public Map<String, String[]> getParameterMap() {
if (!needRequestRestore) return super.getParameterMap();
MultivaluedHashMap<String, String> formParams = getParams();
if (formParams == null) {
return super.getParameterMap();
}
Map<String, String[]> map = new HashMap<>();
Enumeration<String> names = getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String[] values = getParameterValues(name);
if (values != null) {
map.put(name, values);
}
}
return map;
}
@Override
public String getParameter(String name) {
if (!needRequestRestore) return super.getParameter(name);
String param = super.getParameter(name);
if (param != null) return param;
MultivaluedHashMap<String, String> formParams = getParams();
if (formParams == null) {
return null;
}
return formParams.getFirst(name);
}
@Override
public BufferedReader getReader() throws IOException {
if (!needRequestRestore) return super.getReader();
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public int getContentLength() {
if (!needRequestRestore) return super.getContentLength();
String header = getHeader("content-length");
if (header == null) return -1;
return Integer.valueOf(header);
}
@Override
public String getContentType() {
if (!needRequestRestore) return super.getContentType();
return getHeader("content-type");
}
@Override
public String getCharacterEncoding() {
if (!needRequestRestore) return super.getCharacterEncoding();
return getCharsetFromContentType(getContentType());
}
};
return wrapper;
} else {
return new HttpServletRequestWrapper(request) {
@Override
public boolean isUserInRole(String role) {
return account.getRoles().contains(role);
}
@Override
public Principal getUserPrincipal() {
return account.getPrincipal();
}
@Override
public void logout() throws ServletException {
servletRequestLogout();
}
};
}
}
public String getRedirectUri() { public String getRedirectUri() {
HttpSession session = request.getSession(true); HttpSession session = request.getSession(true);
return (String)session.getAttribute(REDIRECT_URI); return (String)session.getAttribute(REDIRECT_URI);
@ -50,6 +312,44 @@ public class FilterSessionStore implements AdapterSessionStore {
return session.getAttribute(REDIRECT_URI) != null; return session.getAttribute(REDIRECT_URI) != null;
} }
public static MultivaluedHashMap<String, String> parseForm(InputStream entityStream)
throws IOException
{
char[] buffer = new char[100];
StringBuffer buf = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(entityStream));
int wasRead = 0;
do
{
wasRead = reader.read(buffer, 0, 100);
if (wasRead > 0) buf.append(buffer, 0, wasRead);
} while (wasRead > -1);
String form = buf.toString();
MultivaluedHashMap<String, String> formData = new MultivaluedHashMap<String, String>();
if ("".equals(form)) return formData;
String[] params = form.split("&");
for (String param : params)
{
if (param.indexOf('=') >= 0)
{
String[] nv = param.split("=");
String val = nv.length > 1 ? nv[1] : "";
formData.add(Encode.decode(nv[0]), Encode.decode(val));
}
else
{
formData.add(Encode.decode(param), "");
}
}
return formData;
}
@Override @Override
public void saveRequest() { public void saveRequest() {
@ -62,7 +362,7 @@ public class FilterSessionStore implements AdapterSessionStore {
String name = names.nextElement(); String name = names.nextElement();
Enumeration<String> values = request.getHeaders(name); Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) { while (values.hasMoreElements()) {
headers.add(name, values.nextElement()); headers.add(name.toLowerCase(), values.nextElement());
} }
} }
session.setAttribute(SAVED_HEADERS, headers); session.setAttribute(SAVED_HEADERS, headers);
@ -93,6 +393,8 @@ public class FilterSessionStore implements AdapterSessionStore {
if (body.length > 0) { if (body.length > 0) {
session.setAttribute(SAVED_BODY, body); session.setAttribute(SAVED_BODY, body);
} }
} }
} }

View file

@ -67,6 +67,13 @@ public class ServletHttpFacade implements HttpFacade {
return queryParameters.getFirst(param); return queryParameters.getFirst(param);
} }
public MultivaluedHashMap<String, String> getQueryParameters() {
if (queryParameters == null) {
queryParameters = UriUtils.decodeQueryString(request.getQueryString());
}
return queryParameters;
}
@Override @Override
public Cookie getCookie(String cookieName) { public Cookie getCookie(String cookieName) {
if (request.getCookies() == null) return null; if (request.getCookies() == null) return null;

View file

@ -0,0 +1,83 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.6.0.Final-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<name>Keycloak Servlet Filter Adapter Integration</name>
<description/>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,86 @@
package org.keycloak.adapters.servlet;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.security.Principal;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
* @version $Revision: 1 $
*/
public class FilterRequestAuthenticator extends RequestAuthenticator {
private static final Logger log = Logger.getLogger(""+FilterRequestAuthenticator.class);
protected HttpServletRequest request;
public FilterRequestAuthenticator(KeycloakDeployment deployment,
AdapterTokenStore tokenStore,
OIDCHttpFacade facade,
HttpServletRequest request,
int sslRedirectPort) {
super(facade, deployment, tokenStore, sslRedirectPort);
this.request = request;
}
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
}
@Override
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
OidcKeycloakAccount account = new OidcKeycloakAccount() {
@Override
public Principal getPrincipal() {
return skp;
}
@Override
public Set<String> getRoles() {
return roles;
}
@Override
public KeycloakSecurityContext getKeycloakSecurityContext() {
return securityContext;
}
};
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
this.tokenStore.saveAccountInfo(account);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isLoggable(Level.FINE)) {
log.fine("Completing bearer authentication. Bearer roles: " + roles);
}
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
@Override
protected String getHttpSessionId(boolean create) {
HttpSession session = request.getSession(create);
return session != null ? session.getId() : null;
}
}

View file

@ -0,0 +1,162 @@
package org.keycloak.adapters.servlet;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.InMemorySessionIdMapper;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.SessionIdMapper;
import org.keycloak.adapters.UserSessionManagement;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakOIDCFilter implements Filter {
protected AdapterDeploymentContext deploymentContext;
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
protected NodesRegistrationManagement nodesRegistrationManagement;
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) {
try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else {
String fp = filterConfig.getInitParameter("keycloak.config.file");
InputStream is = null;
if (fp != null) {
try {
is = new FileInputStream(fp);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String path = "/WEB-INF/keycloak.json";
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
KeycloakDeployment kd;
if (is == null) {
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
kd = new KeycloakDeployment();
} else {
kd = KeycloakDeploymentBuilder.build(is);
}
deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration.");
}
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
nodesRegistrationManagement = new NodesRegistrationManagement();
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
OIDCServletHttpFacade facade = new OIDCServletHttpFacade(request, response);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
response.sendError(403);
log.fine("deployment not configured");
return;
}
PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
@Override
public void logoutAll() {
if (idMapper != null) {
idMapper.clear();
}
}
@Override
public void logoutHttpSessions(List<String> ids) {
for (String id : ids) {
idMapper.removeSession(id);
}
}
}, deploymentContext, facade);
if (preActions.handleRequest()) {
return;
}
nodesRegistrationManagement.tryRegister(deployment);
OIDCFilterSessionStore tokenStore = new OIDCFilterSessionStore(request, facade, 100000, deployment, idMapper);
tokenStore.checkCurrentToken();
FilterRequestAuthenticator authenticator = new FilterRequestAuthenticator(deployment, tokenStore, facade, request, 8443);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
log.fine("AUTHENTICATED");
if (facade.isEnded()) {
return;
}
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
if (actions.handledRequest()) {
return;
} else {
HttpServletRequestWrapper wrapper = tokenStore.buildWrapper();
chain.doFilter(wrapper, res);
return;
}
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
log.fine("challenge");
challenge.challenge(facade);
if (challenge.errorPage()) {
response.sendError(challenge.getResponseCode());
return;
}
log.fine("sending challenge");
return;
}
response.sendError(403);
}
@Override
public void destroy() {
}
}

View file

@ -0,0 +1,167 @@
package org.keycloak.adapters.servlet;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.SessionIdMapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.security.Principal;
import java.util.Set;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCFilterSessionStore extends FilterSessionStore implements AdapterTokenStore {
protected final KeycloakDeployment deployment;
private static final Logger log = Logger.getLogger("" + OIDCFilterSessionStore.class);
protected final SessionIdMapper idMapper;
public OIDCFilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, KeycloakDeployment deployment, SessionIdMapper idMapper) {
super(request, facade, maxBuffer);
this.deployment = deployment;
this.idMapper = idMapper;
}
public HttpServletRequestWrapper buildWrapper() {
HttpSession session = request.getSession();
KeycloakAccount account = (KeycloakAccount)session.getAttribute((KeycloakAccount.class.getName()));
return buildWrapper(session, account);
}
@Override
public void checkCurrentToken() {
HttpSession httpSession = request.getSession(false);
if (httpSession == null) return;
SerializableKeycloakAccount account = (SerializableKeycloakAccount)httpSession.getAttribute(KeycloakAccount.class.getName());
if (account == null) {
return;
}
RefreshableKeycloakSecurityContext session = account.getKeycloakSecurityContext();
if (session == null) return;
// just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = session.refreshExpiredToken(false);
if (success && session.isActive()) return;
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
//log.fine("Cleanup and expire session " + httpSession.getId() + " after failed refresh");
cleanSession(httpSession);
httpSession.invalidate();
}
protected void cleanSession(HttpSession session) {
session.removeAttribute(KeycloakAccount.class.getName());
clearSavedRequest(session);
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
HttpSession httpSession = request.getSession(false);
if (httpSession == null) return false;
SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
if (account == null) {
return false;
}
log.fine("remote logged in already. Establish state from session");
RefreshableKeycloakSecurityContext securityContext = account.getKeycloakSecurityContext();
if (!deployment.getRealm().equals(securityContext.getRealm())) {
log.fine("Account from cookie is from a different realm than for the request.");
cleanSession(httpSession);
return false;
}
if (idMapper != null && !idMapper.hasSession(httpSession.getId())) {
cleanSession(httpSession);
return false;
}
securityContext.setCurrentRequestInfo(deployment, this);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
needRequestRestore = restoreRequest();
return true;
}
public static class SerializableKeycloakAccount implements OidcKeycloakAccount, Serializable {
protected Set<String> roles;
protected Principal principal;
protected RefreshableKeycloakSecurityContext securityContext;
public SerializableKeycloakAccount(Set<String> roles, Principal principal, RefreshableKeycloakSecurityContext securityContext) {
this.roles = roles;
this.principal = principal;
this.securityContext = securityContext;
}
@Override
public Principal getPrincipal() {
return principal;
}
@Override
public Set<String> getRoles() {
return roles;
}
@Override
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
return securityContext;
}
}
@Override
public void saveAccountInfo(OidcKeycloakAccount account) {
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
Set<String> roles = account.getRoles();
SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
}
@Override
public void logout() {
HttpSession httpSession = request.getSession(false);
if (httpSession != null) {
SerializableKeycloakAccount account = (SerializableKeycloakAccount) httpSession.getAttribute(KeycloakAccount.class.getName());
if (account != null) {
account.getKeycloakSecurityContext().logout(deployment);
}
cleanSession(httpSession);
}
}
@Override
public void servletRequestLogout() {
logout();
}
@Override
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
// no-op
}
}

View file

@ -0,0 +1,23 @@
package org.keycloak.adapters.servlet;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCServletHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
public OIDCServletHttpFacade(HttpServletRequest request, HttpServletResponse response) {
super(request, response);
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
}
}

View file

@ -77,6 +77,13 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
request.setUserPrincipal(null); request.setUserPrincipal(null);
} }
protected void beforeStop() {
if (nodesRegistrationManagement != null) {
nodesRegistrationManagement.stop();
}
}
@SuppressWarnings("UseSpecificCatch") @SuppressWarnings("UseSpecificCatch")
public void keycloakInit() { public void keycloakInit() {
// Possible scenarios: // Possible scenarios:
@ -119,11 +126,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
nodesRegistrationManagement = new NodesRegistrationManagement(); nodesRegistrationManagement = new NodesRegistrationManagement();
} }
protected void beforeStop() {
if (nodesRegistrationManagement != null) {
nodesRegistrationManagement.stop();
}
}
private static InputStream getJSONFromServletContext(ServletContext servletContext) { private static InputStream getJSONFromServletContext(ServletContext servletContext) {
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);

View file

@ -894,7 +894,12 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-server-filter-adapter</artifactId> <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -31,6 +31,11 @@ public class InitiateLogin implements AuthChallenge {
return false; return false;
} }
@Override
public int getResponseCode() {
return 0;
}
@Override @Override
public boolean challenge(HttpFacade httpFacade) { public boolean challenge(HttpFacade httpFacade) {
try { try {

View file

@ -259,6 +259,7 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
challenge.challenge(facade);
if (challenge.errorPage() && errorPage != null) { if (challenge.errorPage() && errorPage != null) {
Response response = (Response)res; Response response = (Response)res;
try { try {
@ -268,7 +269,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
} }
} }
challenge.challenge(facade);
} }
return Authentication.SEND_CONTINUE; return Authentication.SEND_CONTINUE;
} }

View file

@ -9,7 +9,7 @@
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-server-filter-adapter</artifactId> <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
<name>Keycloak SAML Servlet Filter</name> <name>Keycloak SAML Servlet Filter</name>
<description /> <description />

View file

@ -2,6 +2,7 @@ package org.keycloak.adapters.saml.servlet;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.HttpFacade; import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.SessionIdMapper; import org.keycloak.adapters.SessionIdMapper;
import org.keycloak.adapters.saml.SamlSession; import org.keycloak.adapters.saml.SamlSession;
import org.keycloak.adapters.saml.SamlSessionStore; import org.keycloak.adapters.saml.SamlSessionStore;
@ -34,8 +35,6 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
this.idMapper = idMapper; this.idMapper = idMapper;
} }
protected boolean needRequestRestore;
@Override @Override
public void logoutAccount() { public void logoutAccount() {
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
@ -107,91 +106,8 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
public HttpServletRequestWrapper getWrap() { public HttpServletRequestWrapper getWrap() {
HttpSession session = request.getSession(true); HttpSession session = request.getSession(true);
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName()); final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
if (needRequestRestore) { final KeycloakAccount account = samlSession;
final String method = (String)session.getAttribute(SAVED_METHOD); return buildWrapper(session, account);
final byte[] body = (byte[])session.getAttribute(SAVED_BODY);
final MultivaluedHashMap<String, String> headers = (MultivaluedHashMap<String, String>)session.getAttribute(SAVED_HEADERS);
clearSavedRequest(session);
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
@Override
public boolean isUserInRole(String role) {
return samlSession.getRoles().contains(role);
}
@Override
public Principal getUserPrincipal() {
return samlSession.getPrincipal();
}
@Override
public String getMethod() {
if (needRequestRestore) {
return method;
} else {
return super.getMethod();
}
}
@Override
public String getHeader(String name) {
if (needRequestRestore && headers != null) {
return headers.getFirst(name);
}
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaders(String name) {
if (needRequestRestore && headers != null) {
List<String> values = headers.getList(name);
if (values == null) return Collections.emptyEnumeration();
else return Collections.enumeration(values);
}
return super.getHeaders(name);
}
@Override
public Enumeration<String> getHeaderNames() {
if (needRequestRestore && headers != null) {
return Collections.enumeration(headers.keySet());
}
return super.getHeaderNames();
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (needRequestRestore && body != null) {
final ByteArrayInputStream is = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return is.read();
}
};
}
return super.getInputStream();
}
};
return wrapper;
} else {
return new HttpServletRequestWrapper(request) {
@Override
public boolean isUserInRole(String role) {
return samlSession.getRoles().contains(role);
}
@Override
public Principal getUserPrincipal() {
return samlSession.getPrincipal();
}
};
}
} }
@Override @Override

View file

@ -24,6 +24,8 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.logging.Level; import java.util.logging.Level;
@ -52,10 +54,20 @@ public class SamlFilter implements Filter {
//deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment()); //deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
} }
} else { } else {
String path = "/WEB-INF/keycloak-saml.xml"; String fp = filterConfig.getInitParameter("keycloak.config.file");
String pathParam = filterConfig.getInitParameter("keycloak.config.file"); InputStream is = null;
if (pathParam != null) path = pathParam; if (fp != null) {
InputStream is = filterConfig.getServletContext().getResourceAsStream(path); try {
is = new FileInputStream(fp);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String path = "/WEB-INF/keycloak-saml.xml";
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
final SamlDeployment deployment; final SamlDeployment deployment;
if (is == null) { if (is == null) {
log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests."); log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
@ -124,19 +136,17 @@ public class SamlFilter implements Filter {
AuthChallenge challenge = authenticator.getChallenge(); AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) { if (challenge != null) {
log.fine("challenge"); log.fine("challenge");
challenge.challenge(facade);
if (challenge.errorPage()) { if (challenge.errorPage()) {
response.sendError(403); response.sendError(challenge.getResponseCode());
return; return;
} }
log.fine("sending challenge"); log.fine("sending challenge");
challenge.challenge(facade);
}
if (outcome == AuthOutcome.FAILED) {
response.sendError(403);
} else if (!facade.isEnded()) {
chain.doFilter(req, res);
return; return;
} }
if (!facade.isEnded()) {
response.sendError(403);
}
} }

View file

@ -212,14 +212,12 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
if (loginConfig == null) { if (loginConfig == null) {
loginConfig = request.getContext().getLoginConfig(); loginConfig = request.getContext().getLoginConfig();
} }
challenge.challenge(facade);
if (challenge.errorPage()) { if (challenge.errorPage()) {
log.fine("error page"); log.fine("error page");
if (forwardToErrorPageInternal(request, response, loginConfig))return false; if (forwardToErrorPageInternal(request, response, loginConfig))return false;
} }
log.fine("sending challenge");
challenge.challenge(facade);
} }
log.fine("No challenge, but failed authentication");
return false; return false;
} }

View file

@ -17,7 +17,6 @@ import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.RestartLoginCookie; import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -310,9 +309,12 @@ public class AuthorizationEndpoint {
private Response buildRegister() { private Response buildRegister() {
authManager.expireIdentityCookie(realm, uriInfo, clientConnection); authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
return session.getProvider(LoginFormsProvider.class) AuthenticationFlowModel flow = realm.getRegistrationFlow();
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()) String flowId = flow.getId();
.createRegistration();
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.REGISTRATION_PATH);
return processor.authenticate();
} }
private Response buildForgotCredential() { private Response buildForgotCredential() {

View file

@ -0,0 +1,16 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ClientRegistrationProvider extends Provider {
void setRealm(RealmModel realm);
void setEvent(EventBuilder event);
}

View file

@ -0,0 +1,9 @@
package org.keycloak.services.clientregistration;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ClientRegistrationProviderFactory extends ProviderFactory<ClientRegistrationProvider> {
}

View file

@ -0,0 +1,37 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AppAuthManager;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationService {
private RealmModel realm;
private EventBuilder event;
@Context
private KeycloakSession session;
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.event = event;
}
@Path("{provider}")
public Object getProvider(@PathParam("provider") String providerId) {
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
provider.setRealm(realm);
provider.setEvent(event);
return provider;
}
}

View file

@ -0,0 +1,30 @@
package org.keycloak.services.clientregistration;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "client-registration";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientRegistrationProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientRegistrationProviderFactory.class;
}
}

View file

@ -1,14 +1,9 @@
package org.keycloak.services.resources; package org.keycloak.services.clientregistration;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.BadRequestException;
import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.exportimport.ClientDescriptionConverter;
import org.keycloak.exportimport.KeycloakClientDescriptionConverter;
import org.keycloak.models.*; import org.keycloak.models.*;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.models.utils.RepresentationToModel;
@ -16,13 +11,11 @@ import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ForbiddenException; import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.*; import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -31,52 +24,37 @@ import java.net.URI;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class ClientRegistrationService { public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
protected static final Logger logger = Logger.getLogger(ClientRegistrationService.class); private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private RealmModel realm; private RealmModel realm;
private EventBuilder event; public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
@Context
private KeycloakSession session;
private AppAuthManager authManager = new AppAuthManager();
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
this.realm = realm;
this.event = event;
} }
@POST @POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN }) @Consumes(MediaType.APPLICATION_JSON)
public Response create(String description, @QueryParam("format") String format) { public Response create(ClientRepresentation client) {
event.event(EventType.CLIENT_REGISTER); event.event(EventType.CLIENT_REGISTER);
authenticate(true, null); authenticate(true, null);
if (format == null) {
format = KeycloakClientDescriptionConverter.ID;
}
ClientDescriptionConverter converter = session.getProvider(ClientDescriptionConverter.class, format);
if (converter == null) {
throw new BadRequestException("Invalid format");
}
ClientRepresentation rep = converter.convertToInternal(description);
try { try {
ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true); ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
rep = ModelToRepresentation.toRepresentation(clientModel); client = ModelToRepresentation.toRepresentation(clientModel);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build(); URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
logger.infov("Created client {0}", rep.getClientId()); logger.infov("Created client {0}", client.getClientId());
event.client(rep.getClientId()).success(); event.client(client.getClientId()).success();
return Response.created(uri).entity(rep).build(); return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) { } catch (ModelDuplicateException e) {
return ErrorResponse.exists("Client " + rep.getClientId() + " already exists"); return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
} }
} }
@ -122,13 +100,19 @@ public class ClientRegistrationService {
} }
} }
@Override
public void close() {
}
private ClientModel authenticate(boolean create, String clientId) { private ClientModel authenticate(boolean create, String clientId) {
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer"); boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
if (bearer) { if (bearer) {
AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm); AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID); AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
if (realmAccess != null) { if (realmAccess != null) {
if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) { if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
@ -158,4 +142,14 @@ public class ClientRegistrationService {
throw new ForbiddenException(); throw new ForbiddenException();
} }
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
} }

View file

@ -0,0 +1,34 @@
package org.keycloak.services.clientregistration;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DefaultClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new DefaultClientRegistrationProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private RealmModel realm;
private EventBuilder event;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void close() {
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.services.clientregistration;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new OIDCClientRegistrationProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "openid-connect";
}
}

View file

@ -27,6 +27,8 @@ public class KeycloakSessionServletFilter implements Filter {
@Override @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
final HttpServletRequest request = (HttpServletRequest)servletRequest; final HttpServletRequest request = (HttpServletRequest)servletRequest;
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName()); KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());

View file

@ -13,6 +13,7 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory; import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.services.clientregistration.ClientRegistrationService;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;

View file

@ -323,6 +323,14 @@ public class AuthenticationManagementResource {
if (flow.isBuiltIn()) { if (flow.isBuiltIn()) {
throw new BadRequestException("Can't delete built in flow"); throw new BadRequestException("Can't delete built in flow");
} }
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(id);
for (AuthenticationExecutionModel execution : executions) {
if(execution.getFlowId() != null) {
AuthenticationFlowModel nonTopLevelFlow = realm.getAuthenticationFlowById(execution.getFlowId());
realm.removeAuthenticationFlow(nonTopLevelFlow);
}
realm.removeAuthenticatorExecution(execution);
}
realm.removeAuthenticationFlow(flow); realm.removeAuthenticationFlow(flow);
} }

View file

@ -8,3 +8,4 @@ org.keycloak.authentication.ClientAuthenticatorSpi
org.keycloak.authentication.RequiredActionSpi org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.FormAuthenticatorSpi org.keycloak.authentication.FormAuthenticatorSpi
org.keycloak.authentication.FormActionSpi org.keycloak.authentication.FormActionSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi

View file

@ -0,0 +1 @@
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory

View file

@ -6,13 +6,7 @@ import io.undertow.servlet.api.DefaultServletConfig;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.ServletInfo; import io.undertow.servlet.api.ServletInfo;
import java.util.Collection;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer; import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException; import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.container.LifecycleException; import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
@ -25,10 +19,13 @@ import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor; import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive; import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive;
import org.keycloak.services.filters.ClientConnectionFilter;
import org.keycloak.services.filters.KeycloakSessionServletFilter; import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import javax.servlet.DispatcherType;
import java.util.Collection;
import java.util.Map;
public class CustomUndertowContainer implements DeployableContainer<CustomUndertowContainerConfiguration> { public class CustomUndertowContainer implements DeployableContainer<CustomUndertowContainerConfiguration> {
protected final Logger log = Logger.getLogger(this.getClass()); protected final Logger log = Logger.getLogger(this.getClass());
@ -52,10 +49,6 @@ public class CustomUndertowContainer implements DeployableContainer<CustomUndert
di.addFilter(filter); di.addFilter(filter);
di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST); di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST);
FilterInfo connectionFilter = Servlets.filter("ClientConnectionFilter", ClientConnectionFilter.class);
di.addFilter(connectionFilter);
di.addFilterUrlMapping("ClientConnectionFilter", "/*", DispatcherType.REQUEST);
return di; return di;
} }

View file

@ -24,6 +24,7 @@ import org.junit.Assert;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD; import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
@ -46,6 +47,7 @@ import org.openqa.selenium.Cookie;
* *
* @author tkyjovsk * @author tkyjovsk
*/ */
@Ignore
public class LoginSettingsTest extends AbstractRealmTest { public class LoginSettingsTest extends AbstractRealmTest {
private static final String NEW_USERNAME = "newUsername"; private static final String NEW_USERNAME = "newUsername";

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.realm;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.keycloak.testsuite.auth.page.account.Account; import org.keycloak.testsuite.auth.page.account.Account;
import org.keycloak.testsuite.console.page.realm.BruteForceDetection; import org.keycloak.testsuite.console.page.realm.BruteForceDetection;
@ -38,6 +39,7 @@ import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
* @author Filip Kiss * @author Filip Kiss
* @author mhajas * @author mhajas
*/ */
@Ignore
public class SecurityDefensesTest extends AbstractRealmTest { public class SecurityDefensesTest extends AbstractRealmTest {
@Page @Page

View file

@ -107,7 +107,11 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-server-filter-adapter</artifactId> <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-filter-adapter</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>

View file

@ -604,6 +604,7 @@ public class AdapterTestStrategy extends ExternalResource {
// logout sessions in account management // logout sessions in account management
accountSessionsPage.realm("demo"); accountSessionsPage.realm("demo");
accountSessionsPage.open(); accountSessionsPage.open();
Assert.assertTrue(accountSessionsPage.isCurrent());
accountSessionsPage.logoutAll(); accountSessionsPage.logoutAll();
// Assert I need to login again (logout was propagated to the app) // Assert I need to login again (logout was propagated to the app)

View file

@ -0,0 +1,214 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.testsuite.adapter;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import java.net.URL;
import java.security.PublicKey;
/**
* Tests Undertow Adapter
*
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/
public class FilterAdapterTest {
public static PublicKey realmPublicKey;
@ClassRule
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
@Override
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
RealmModel realm = AdapterTestStrategy.baseAdapterTestInitialization(session, manager, adminRealm, getClass());
realmPublicKey = realm.getPublicKey();
URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
createApplicationDeployment()
.name("customer-portal").contextPath("/customer-portal")
.servletClass(CustomerServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplicationWithFilter();
url = getClass().getResource("/adapter-test/secure-portal-keycloak.json");
createApplicationDeployment()
.name("secure-portal").contextPath("/secure-portal")
.servletClass(CallAuthenticatedServlet.class).adapterConfigPath(url.getPath())
.role("user")
.isConstrained(false).deployApplicationWithFilter();
url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
createApplicationDeployment()
.name("customer-db").contextPath("/customer-db")
.servletClass(CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
.role("user")
.errorPage(null).deployApplicationWithFilter();
createApplicationDeployment()
.name("customer-db-error-page").contextPath("/customer-db-error-page")
.servletClass(CustomerDatabaseServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplicationWithFilter();
url = getClass().getResource("/adapter-test/product-keycloak.json");
createApplicationDeployment()
.name("product-portal").contextPath("/product-portal")
.servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplicationWithFilter();
// Test that replacing system properties works for adapters
System.setProperty("app.server.base.url", "http://localhost:8081");
System.setProperty("my.host.name", "localhost");
url = getClass().getResource("/adapter-test/session-keycloak.json");
createApplicationDeployment()
.name("session-portal").contextPath("/session-portal")
.servletClass(SessionServlet.class).adapterConfigPath(url.getPath())
.role("user").deployApplicationWithFilter();
url = getClass().getResource("/adapter-test/input-keycloak.json");
createApplicationDeployment()
.name("input-portal").contextPath("/input-portal")
.servletClass(InputServlet.class).adapterConfigPath(url.getPath())
.role("user").constraintUrl("/secured/*").deployApplicationWithFilter();
}
};
@Rule
public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
@Test
public void testLoginSSOAndLogout() throws Exception {
testStrategy.testLoginSSOAndLogout();
}
@Test
public void testSavedPostRequest() throws Exception {
testStrategy.testSavedPostRequest();
}
@Test
public void testServletRequestLogout() throws Exception {
testStrategy.testServletRequestLogout();
}
@Test
public void testLoginSSOIdle() throws Exception {
testStrategy.testLoginSSOIdle();
}
@Test
public void testLoginSSOIdleRemoveExpiredUserSessions() throws Exception {
testStrategy.testLoginSSOIdleRemoveExpiredUserSessions();
}
@Test
public void testLoginSSOMax() throws Exception {
testStrategy.testLoginSSOMax();
}
/**
* KEYCLOAK-518
* @throws Exception
*/
@Test
public void testNullBearerToken() throws Exception {
testStrategy.testNullBearerToken();
}
/**
* KEYCLOAK-1368
* @throws Exception
*/
/*
can't test because of the way filter works
@Test
public void testNullBearerTokenCustomErrorPage() throws Exception {
testStrategy.testNullBearerTokenCustomErrorPage();
}
*/
/**
* KEYCLOAK-518
* @throws Exception
*/
@Test
public void testBadUser() throws Exception {
testStrategy.testBadUser();
}
@Test
public void testVersion() throws Exception {
testStrategy.testVersion();
}
/*
Don't need to test this because HttpServletRequest.authenticate doesn't make sense with filter implementation
@Test
public void testAuthenticated() throws Exception {
testStrategy.testAuthenticated();
}
*/
/**
* KEYCLOAK-732
*
* @throws Throwable
*/
@Test
public void testSingleSessionInvalidated() throws Throwable {
testStrategy.testSingleSessionInvalidated();
}
/**
* KEYCLOAK-741
*/
@Test
public void testSessionInvalidatedAfterFailedRefresh() throws Throwable {
testStrategy.testSessionInvalidatedAfterFailedRefresh();
}
/**
* KEYCLOAK-942
*/
@Test
public void testAdminApplicationLogout() throws Throwable {
testStrategy.testAdminApplicationLogout();
}
/**
* KEYCLOAK-1216
*/
/*
Can't test this because backchannel logout for filter does not invalidate the session
@Test
public void testAccountManagementSessionsLogout() throws Throwable {
testStrategy.testAccountManagementSessionsLogout();
}
*/
}

View file

@ -1,6 +1,7 @@
package org.keycloak.testsuite.rule; package org.keycloak.testsuite.rule;
import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.FilterInfo;
import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.SecurityConstraint; import io.undertow.servlet.api.SecurityConstraint;
import io.undertow.servlet.api.SecurityInfo; import io.undertow.servlet.api.SecurityInfo;
@ -11,6 +12,8 @@ import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.saml.servlet.SamlFilter;
import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -24,6 +27,7 @@ import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import javax.servlet.DispatcherType;
import javax.servlet.Servlet; import javax.servlet.Servlet;
import javax.ws.rs.core.Application; import javax.ws.rs.core.Application;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -350,6 +354,22 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
server.getServer().deploy(di); server.getServer().deploy(di);
} }
public void deployApplicationWithFilter() {
DeploymentInfo di = createDeploymentInfo(name, contextPath, servletClass);
FilterInfo filter = new FilterInfo("keycloak-filter", KeycloakOIDCFilter.class);
if (null == keycloakConfigResolver) {
filter.addInitParam("keycloak.config.file", adapterConfigPath);
} else {
filter.addInitParam("keycloak.config.resolver", keycloakConfigResolver.getCanonicalName());
}
di.addFilter(filter);
di.addFilterUrlMapping("keycloak-filter", constraintUrl, DispatcherType.REQUEST);
server.getServer().deploy(di);
}
} }
} }