Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
1d829a8365
61 changed files with 1888 additions and 232 deletions
|
@ -231,7 +231,7 @@ public class ClientRegistration {
|
|||
|
||||
public ClientRegistration build() {
|
||||
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.auth = auth;
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<!ENTITY ProtocolMappers SYSTEM "modules/protocol-mappers.xml">
|
||||
<!ENTITY Recaptcha SYSTEM "modules/recaptcha.xml">
|
||||
<!ENTITY AuthSPI SYSTEM "modules/auth-spi.xml">
|
||||
<!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
|
||||
]>
|
||||
|
||||
<book>
|
||||
|
@ -105,6 +106,7 @@ This one is short
|
|||
&TomcatAdapter;
|
||||
&Jetty9Adapter;
|
||||
&Jetty8Adapter;
|
||||
&FilterAdapter;
|
||||
&FuseAdapter;
|
||||
&JavascriptAdapter;
|
||||
&SpringBootAdapter;
|
||||
|
|
|
@ -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>
|
|
@ -7,6 +7,7 @@
|
|||
<!ENTITY TomcatAdapter SYSTEM "modules/tomcat-adapter.xml">
|
||||
<!ENTITY Jetty9Adapter SYSTEM "modules/jetty9-adapter.xml">
|
||||
<!ENTITY Jetty8Adapter SYSTEM "modules/jetty8-adapter.xml">
|
||||
<!ENTITY FilterAdapter SYSTEM "modules/servlet-filter-adapter.xml">
|
||||
<!ENTITY Logout SYSTEM "modules/logout.xml">
|
||||
]>
|
||||
|
||||
|
@ -46,6 +47,7 @@ This one is short
|
|||
&TomcatAdapter;
|
||||
&Jetty9Adapter;
|
||||
&Jetty8Adapter;
|
||||
&FilterAdapter;
|
||||
&Logout;
|
||||
|
||||
|
||||
|
|
|
@ -59,13 +59,13 @@
|
|||
<para>
|
||||
Here is the explanation of the SP element attributes
|
||||
</para>
|
||||
<para><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<SP entityID="sp"
|
||||
sslPolicy="ssl"
|
||||
nameIDPolicyFormat="format"
|
||||
forceAuthentication="true">
|
||||
...
|
||||
</SP>]]></para>
|
||||
</SP>]]></programlisting>
|
||||
<para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
|
@ -129,7 +129,7 @@
|
|||
or you can cut and paste the keys directly within <literal>keycloak-saml.xml</literal>
|
||||
in the PEM format.
|
||||
</para>
|
||||
<para><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<Keys>
|
||||
<Key signing="true" >
|
||||
<KeyStore resource="/WEB-INF/keystore.jks" password="store123">
|
||||
|
@ -139,7 +139,7 @@
|
|||
</Key>
|
||||
</Keys>
|
||||
]]>
|
||||
</para>
|
||||
</programlisting>
|
||||
<para>
|
||||
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
|
||||
|
@ -215,13 +215,13 @@
|
|||
</section>
|
||||
<section>
|
||||
<title>RoleIdentifiers element</title>
|
||||
<para><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<RoleIdentifiers>
|
||||
<Attribute name="Role"/>
|
||||
<Attribute name="member"/>
|
||||
<Attribute name="memberOf"/>
|
||||
</RoleIdentifiers>
|
||||
]]></para>
|
||||
]]></programlisting>
|
||||
<para>
|
||||
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
|
||||
|
@ -236,7 +236,7 @@
|
|||
Everything in the IDP element describes the settings for the IDP the SP is communicating
|
||||
with.
|
||||
</para>
|
||||
<para>
|
||||
<programlisting>
|
||||
<![CDATA[
|
||||
<IDP entityID="idp"
|
||||
signaturesRequired="true"
|
||||
|
@ -244,7 +244,7 @@
|
|||
signatureCanonicalizationMethod="http://www.w3.org/2001/10/xml-exc-c14n#">
|
||||
...
|
||||
</IDP>]]>
|
||||
</para>
|
||||
</programlisting>
|
||||
<para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
|
@ -300,12 +300,12 @@
|
|||
The <literal>SignleSignOnService</literal> sub element defines the
|
||||
login SAML endpoint of the IDP.
|
||||
</para>
|
||||
<para><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<SingleSignOnService signRequest="true"
|
||||
validateResponseSignature="true"
|
||||
requestBinding="post"
|
||||
bindingUrl="url"/>
|
||||
]]></para>
|
||||
]]></programlisting>
|
||||
<para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
|
@ -367,7 +367,7 @@
|
|||
The <literal>SignleSignOnService</literal> sub element defines the
|
||||
login SAML endpoint of the IDP.
|
||||
</para>
|
||||
<para><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<SingleLogoutService validateRequestSignature="true"
|
||||
validateResponseSignature="true"
|
||||
signRequest="true"
|
||||
|
@ -376,7 +376,7 @@
|
|||
responseBinding="post"
|
||||
postBindingUrl="posturl"
|
||||
redirectBindingUrl="redirecturl">
|
||||
]]></para>
|
||||
]]></programlisting>
|
||||
<para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
|
|
|
@ -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>
|
|
@ -126,3 +126,4 @@ locale_de=Deutsch
|
|||
locale_en=English
|
||||
locale_it=Italian
|
||||
locale_pt-BR=Portugu\u00EAs (Brasil)
|
||||
locale_fr=Français
|
|
@ -150,3 +150,4 @@ locale_de=German
|
|||
locale_en=English
|
||||
locale_it=Italian
|
||||
locale_pt-BR=Portugu\u00EAs (Brasil)
|
||||
locale_fr=Français
|
|
@ -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=« 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
|
|
@ -125,3 +125,4 @@ locale_de=German
|
|||
locale_en=English
|
||||
locale_it=Italian
|
||||
locale_pt-BR=Portugu\u00EAs (Brasil)
|
||||
locale_fr=Français
|
|
@ -147,3 +147,4 @@ locale_de=Deutsch
|
|||
locale_en=English
|
||||
locale_it=Italian
|
||||
locale_pt-BR=Portugu\u00EAs (BR)
|
||||
locale_fr=Français
|
|
@ -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.
|
|
@ -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.
|
|
@ -14,16 +14,57 @@ var loadingTimer = -1;
|
|||
angular.element(document).ready(function () {
|
||||
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() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
keycloakAuth.init({ onLoad: 'login-required' }).success(function () {
|
||||
auth.authz = keycloakAuth;
|
||||
|
||||
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();
|
||||
});
|
||||
}).error(function () {
|
||||
window.location.reload();
|
||||
});
|
||||
|
|
|
@ -1,31 +1,9 @@
|
|||
module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $location, Notifications, ServerInfo) {
|
||||
$scope.addMessage = function() {
|
||||
Notifications.success("test");
|
||||
};
|
||||
|
||||
module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location, Notifications, ServerInfo) {
|
||||
$scope.authUrl = authUrl;
|
||||
$scope.resourceUrl = resourceUrl;
|
||||
$scope.auth = Auth;
|
||||
$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) {
|
||||
if (!Current.realm) {
|
||||
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');
|
||||
|
||||
Current.realm = null;
|
||||
|
@ -193,22 +171,17 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
|
|||
}, true);
|
||||
|
||||
$scope.$watch('realm.realm', function() {
|
||||
if (create) {
|
||||
$scope.realm.id = $scope.realm.realm;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
var realmCopy = angular.copy($scope.realm);
|
||||
Realm.create(realmCopy, function() {
|
||||
Realm.query(function(data) {
|
||||
Current.realms = data;
|
||||
|
||||
WhoAmI.get(function(user) {
|
||||
Auth.user = user;
|
||||
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
Notifications.success("The realm has been created.");
|
||||
|
||||
Auth.refreshPermissions(function() {
|
||||
$scope.$apply(function() {
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -227,7 +200,7 @@ module.controller('ObjectModalCtrl', function($scope, 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.serverInfo = serverInfo;
|
||||
|
||||
|
@ -272,11 +245,13 @@ module.controller('RealmDetailCtrl', function($scope, Current, Realm, realm, ser
|
|||
});
|
||||
|
||||
if (nameChanged) {
|
||||
WhoAmI.get(function(user) {
|
||||
Auth.user = user;
|
||||
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
Auth.refreshPermissions(function() {
|
||||
Auth.refreshPermissions(function() {
|
||||
Notifications.success("Your changes have been saved to the realm.");
|
||||
$scope.$apply(function() {
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$location.url("/realms/" + realmCopy.realm);
|
||||
|
@ -1770,6 +1745,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
|
|||
execution.postLevels.push(j);
|
||||
}
|
||||
}
|
||||
$location.url("/realms/" + realm.realm + "/authentication/flows/" + $scope.flow.alias);
|
||||
})
|
||||
};
|
||||
|
||||
|
|
|
@ -184,10 +184,6 @@ module.factory('Notifications', function($rootScope, $timeout) {
|
|||
return notifications;
|
||||
});
|
||||
|
||||
module.factory('WhoAmI', function($resource) {
|
||||
return $resource(consoleBaseUrl + '/whoami');
|
||||
});
|
||||
|
||||
module.factory('Realm', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:id', {
|
||||
id : '@realm'
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<div class="error-container">
|
||||
<h2>Resource <strong>not found</strong>...</h2>
|
||||
<p class="instruction">We could not find the resource you are looking for. Please make sure the URL you entered is correct.</p>
|
||||
<a href="#" class="link-right">Go to the home page »</a>
|
||||
<a href="#/" class="link-right">Go to the home page »</a>
|
||||
</div>
|
||||
</div>
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<table class="table table-striped table-bordered">
|
||||
<tr>
|
||||
<td width="20%">Keycloak Version</td>
|
||||
<td width="20%">Server Version</td>
|
||||
<td>{{serverInfo.systemInfo.version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -114,6 +114,11 @@ public class BearerTokenRequestAuthenticator {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
// do the same thing as client cert auth
|
||||
|
@ -139,6 +144,11 @@ public class BearerTokenRequestAuthenticator {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 401;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade facade) {
|
||||
facade.getResponse().setStatus(401);
|
||||
|
|
|
@ -181,6 +181,11 @@ public class OAuthRequestAuthenticator {
|
|||
public boolean errorPage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 403;
|
||||
}
|
||||
};
|
||||
}
|
||||
return new AuthChallenge() {
|
||||
|
@ -190,6 +195,11 @@ public class OAuthRequestAuthenticator {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
tokenStore.saveRequest();
|
||||
|
@ -262,6 +272,11 @@ public class OAuthRequestAuthenticator {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade exchange) {
|
||||
exchange.getResponse().setStatus(code);
|
||||
|
|
|
@ -13,9 +13,17 @@ public interface AuthChallenge {
|
|||
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
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,14 @@ public class InMemorySessionIdMapper implements SessionIdMapper {
|
|||
return sessionToSso.containsKey(id) || sessionToPrincipal.containsKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
ssoToSession.clear();
|
||||
sessionToSso.clear();
|
||||
principalToSession.clear();
|
||||
sessionToPrincipal.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getUserSessions(String principal) {
|
||||
Set<String> lookup = principalToSession.get(principal);
|
||||
|
|
|
@ -9,6 +9,8 @@ import java.util.Set;
|
|||
public interface SessionIdMapper {
|
||||
boolean hasSession(String id);
|
||||
|
||||
void clear();
|
||||
|
||||
Set<String> getUserSessions(String principal);
|
||||
|
||||
String getSessionFromSSO(String sso);
|
||||
|
|
|
@ -262,6 +262,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||
}
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
if (challenge.errorPage() && errorPage != null) {
|
||||
Response response = (Response)res;
|
||||
try {
|
||||
|
@ -271,7 +272,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
|
|||
}
|
||||
|
||||
}
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<module>undertow-adapter-spi</module>
|
||||
<module>undertow</module>
|
||||
<module>wildfly</module>
|
||||
<module>servlet-filter</module>
|
||||
<module>js</module>
|
||||
<module>installed</module>
|
||||
<module>admin-client</module>
|
||||
|
|
|
@ -2,14 +2,32 @@ package org.keycloak.adapters.servlet;
|
|||
|
||||
import org.keycloak.adapters.AdapterSessionStore;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.util.Encode;
|
||||
import org.keycloak.util.MultivaluedHashMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
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.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>
|
||||
|
@ -24,6 +42,7 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
protected final HttpFacade facade;
|
||||
protected final int maxBuffer;
|
||||
protected byte[] restoredBuffer = null;
|
||||
protected boolean needRequestRestore;
|
||||
|
||||
public FilterSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer) {
|
||||
this.request = request;
|
||||
|
@ -38,6 +57,249 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
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() {
|
||||
HttpSession session = request.getSession(true);
|
||||
return (String)session.getAttribute(REDIRECT_URI);
|
||||
|
@ -50,6 +312,44 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
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
|
||||
public void saveRequest() {
|
||||
|
@ -62,7 +362,7 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
String name = names.nextElement();
|
||||
Enumeration<String> values = request.getHeaders(name);
|
||||
while (values.hasMoreElements()) {
|
||||
headers.add(name, values.nextElement());
|
||||
headers.add(name.toLowerCase(), values.nextElement());
|
||||
}
|
||||
}
|
||||
session.setAttribute(SAVED_HEADERS, headers);
|
||||
|
@ -93,6 +393,8 @@ public class FilterSessionStore implements AdapterSessionStore {
|
|||
if (body.length > 0) {
|
||||
session.setAttribute(SAVED_BODY, body);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,6 +67,13 @@ public class ServletHttpFacade implements HttpFacade {
|
|||
return queryParameters.getFirst(param);
|
||||
}
|
||||
|
||||
public MultivaluedHashMap<String, String> getQueryParameters() {
|
||||
if (queryParameters == null) {
|
||||
queryParameters = UriUtils.decodeQueryString(request.getQueryString());
|
||||
}
|
||||
return queryParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cookie getCookie(String cookieName) {
|
||||
if (request.getCookies() == null) return null;
|
||||
|
|
83
integration/servlet-filter/pom.xml
Executable file
83
integration/servlet-filter/pom.xml
Executable 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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -77,6 +77,13 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
request.setUserPrincipal(null);
|
||||
}
|
||||
|
||||
protected void beforeStop() {
|
||||
if (nodesRegistrationManagement != null) {
|
||||
nodesRegistrationManagement.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("UseSpecificCatch")
|
||||
public void keycloakInit() {
|
||||
// Possible scenarios:
|
||||
|
@ -119,11 +126,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
|||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
}
|
||||
|
||||
protected void beforeStop() {
|
||||
if (nodesRegistrationManagement != null) {
|
||||
nodesRegistrationManagement.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream getJSONFromServletContext(ServletContext servletContext) {
|
||||
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
|
||||
|
|
7
pom.xml
7
pom.xml
|
@ -894,7 +894,12 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -31,6 +31,11 @@ public class InitiateLogin implements AuthChallenge {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getResponseCode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean challenge(HttpFacade httpFacade) {
|
||||
try {
|
||||
|
|
|
@ -259,6 +259,7 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
|
|||
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
if (challenge.errorPage() && errorPage != null) {
|
||||
Response response = (Response)res;
|
||||
try {
|
||||
|
@ -268,7 +269,6 @@ public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
|
|||
}
|
||||
|
||||
}
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
return Authentication.SEND_CONTINUE;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</parent>
|
||||
<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>
|
||||
<description />
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.adapters.saml.servlet;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.HttpFacade;
|
||||
import org.keycloak.adapters.KeycloakAccount;
|
||||
import org.keycloak.adapters.SessionIdMapper;
|
||||
import org.keycloak.adapters.saml.SamlSession;
|
||||
import org.keycloak.adapters.saml.SamlSessionStore;
|
||||
|
@ -34,8 +35,6 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
|
|||
this.idMapper = idMapper;
|
||||
}
|
||||
|
||||
protected boolean needRequestRestore;
|
||||
|
||||
@Override
|
||||
public void logoutAccount() {
|
||||
HttpSession session = request.getSession(false);
|
||||
|
@ -107,91 +106,8 @@ public class FilterSamlSessionStore extends FilterSessionStore implements SamlSe
|
|||
public HttpServletRequestWrapper getWrap() {
|
||||
HttpSession session = request.getSession(true);
|
||||
final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
|
||||
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) {
|
||||
@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();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
final KeycloakAccount account = samlSession;
|
||||
return buildWrapper(session, account);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,8 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
|
@ -51,11 +53,21 @@ public class SamlFilter implements Filter {
|
|||
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-saml.xml";
|
||||
String pathParam = filterConfig.getInitParameter("keycloak.config.file");
|
||||
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
|
||||
if (pathParam != null) path = pathParam;
|
||||
InputStream is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
}
|
||||
final SamlDeployment deployment;
|
||||
if (is == null) {
|
||||
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();
|
||||
if (challenge != null) {
|
||||
log.fine("challenge");
|
||||
challenge.challenge(facade);
|
||||
if (challenge.errorPage()) {
|
||||
response.sendError(403);
|
||||
response.sendError(challenge.getResponseCode());
|
||||
return;
|
||||
}
|
||||
log.fine("sending challenge");
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
response.sendError(403);
|
||||
} else if (!facade.isEnded()) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
if (!facade.isEnded()) {
|
||||
response.sendError(403);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -212,14 +212,12 @@ public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator i
|
|||
if (loginConfig == null) {
|
||||
loginConfig = request.getContext().getLoginConfig();
|
||||
}
|
||||
challenge.challenge(facade);
|
||||
if (challenge.errorPage()) {
|
||||
log.fine("error page");
|
||||
if (forwardToErrorPageInternal(request, response, loginConfig))return false;
|
||||
}
|
||||
log.fine("sending challenge");
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
log.fine("No challenge, but failed authentication");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.keycloak.models.ClientSessionModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.DefaultAuthenticationFlows;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
|
@ -310,9 +309,12 @@ public class AuthorizationEndpoint {
|
|||
private Response buildRegister() {
|
||||
authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
|
||||
|
||||
return session.getProvider(LoginFormsProvider.class)
|
||||
.setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
|
||||
.createRegistration();
|
||||
AuthenticationFlowModel flow = realm.getRegistrationFlow();
|
||||
String flowId = flow.getId();
|
||||
|
||||
AuthenticationProcessor processor = createProcessor(flowId, LoginActionsService.REGISTRATION_PATH);
|
||||
|
||||
return processor.authenticate();
|
||||
}
|
||||
|
||||
private Response buildForgotCredential() {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,9 @@
|
|||
package org.keycloak.services.resources;
|
||||
package org.keycloak.services.clientregistration;
|
||||
|
||||
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.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.exportimport.ClientDescriptionConverter;
|
||||
import org.keycloak.exportimport.KeycloakClientDescriptionConverter;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
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.idm.ClientRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.ForbiddenException;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
@ -31,52 +24,37 @@ import java.net.URI;
|
|||
/**
|
||||
* @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 EventBuilder event;
|
||||
|
||||
@Context
|
||||
private KeycloakSession session;
|
||||
|
||||
private AppAuthManager authManager = new AppAuthManager();
|
||||
|
||||
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
|
||||
this.realm = realm;
|
||||
this.event = event;
|
||||
public DefaultClientRegistrationProvider(KeycloakSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
|
||||
public Response create(String description, @QueryParam("format") String format) {
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response create(ClientRepresentation client) {
|
||||
event.event(EventType.CLIENT_REGISTER);
|
||||
|
||||
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 {
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, realm, rep, true);
|
||||
rep = ModelToRepresentation.toRepresentation(clientModel);
|
||||
ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
|
||||
client = ModelToRepresentation.toRepresentation(clientModel);
|
||||
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();
|
||||
return Response.created(uri).entity(rep).build();
|
||||
event.client(client.getClientId()).success();
|
||||
return Response.created(uri).entity(client).build();
|
||||
} 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) {
|
||||
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
|
||||
boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("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);
|
||||
if (realmAccess != null) {
|
||||
if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
|
||||
|
@ -158,4 +142,14 @@ public class ClientRegistrationService {
|
|||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealm(RealmModel realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEvent(EventBuilder event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,8 @@ public class KeycloakSessionServletFilter implements Filter {
|
|||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
servletRequest.setCharacterEncoding("UTF-8");
|
||||
|
||||
final HttpServletRequest request = (HttpServletRequest)servletRequest;
|
||||
|
||||
KeycloakSessionFactory sessionFactory = (KeycloakSessionFactory) servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.keycloak.protocol.LoginProtocol;
|
|||
import org.keycloak.protocol.LoginProtocolFactory;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.BruteForceProtector;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
|
|
@ -323,6 +323,14 @@ public class AuthenticationManagementResource {
|
|||
if (flow.isBuiltIn()) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,3 +8,4 @@ org.keycloak.authentication.ClientAuthenticatorSpi
|
|||
org.keycloak.authentication.RequiredActionSpi
|
||||
org.keycloak.authentication.FormAuthenticatorSpi
|
||||
org.keycloak.authentication.FormActionSpi
|
||||
org.keycloak.services.clientregistration.ClientRegistrationSpi
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
|
|
@ -6,13 +6,7 @@ import io.undertow.servlet.api.DefaultServletConfig;
|
|||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.FilterInfo;
|
||||
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.DeploymentException;
|
||||
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
|
||||
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.descriptor.api.Descriptor;
|
||||
import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive;
|
||||
import org.keycloak.services.filters.ClientConnectionFilter;
|
||||
import org.keycloak.services.filters.KeycloakSessionServletFilter;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomUndertowContainer implements DeployableContainer<CustomUndertowContainerConfiguration> {
|
||||
|
||||
protected final Logger log = Logger.getLogger(this.getClass());
|
||||
|
@ -52,10 +49,6 @@ public class CustomUndertowContainer implements DeployableContainer<CustomUndert
|
|||
di.addFilter(filter);
|
||||
di.addFilterUrlMapping("SessionFilter", "/*", DispatcherType.REQUEST);
|
||||
|
||||
FilterInfo connectionFilter = Servlets.filter("ClientConnectionFilter", ClientConnectionFilter.class);
|
||||
di.addFilter(connectionFilter);
|
||||
di.addFilterUrlMapping("ClientConnectionFilter", "/*", DispatcherType.REQUEST);
|
||||
|
||||
return di;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.Assert;
|
|||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -46,6 +47,7 @@ import org.openqa.selenium.Cookie;
|
|||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
@Ignore
|
||||
public class LoginSettingsTest extends AbstractRealmTest {
|
||||
|
||||
private static final String NEW_USERNAME = "newUsername";
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.keycloak.testsuite.console.realm;
|
|||
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.testsuite.auth.page.account.Account;
|
||||
import org.keycloak.testsuite.console.page.realm.BruteForceDetection;
|
||||
|
@ -38,6 +39,7 @@ import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
|
|||
* @author Filip Kiss
|
||||
* @author mhajas
|
||||
*/
|
||||
@Ignore
|
||||
public class SecurityDefensesTest extends AbstractRealmTest {
|
||||
|
||||
@Page
|
||||
|
|
|
@ -107,7 +107,11 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
|
|
@ -604,6 +604,7 @@ public class AdapterTestStrategy extends ExternalResource {
|
|||
// logout sessions in account management
|
||||
accountSessionsPage.realm("demo");
|
||||
accountSessionsPage.open();
|
||||
Assert.assertTrue(accountSessionsPage.isCurrent());
|
||||
accountSessionsPage.logoutAll();
|
||||
|
||||
// Assert I need to login again (logout was propagated to the app)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.testsuite.rule;
|
||||
|
||||
import io.undertow.servlet.api.DeploymentInfo;
|
||||
import io.undertow.servlet.api.FilterInfo;
|
||||
import io.undertow.servlet.api.LoginConfig;
|
||||
import io.undertow.servlet.api.SecurityConstraint;
|
||||
import io.undertow.servlet.api.SecurityInfo;
|
||||
|
@ -11,6 +12,8 @@ import org.junit.rules.ExternalResource;
|
|||
import org.junit.rules.TemporaryFolder;
|
||||
import org.keycloak.Config;
|
||||
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.KeycloakTransaction;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -24,6 +27,7 @@ import org.keycloak.testsuite.KeycloakServer;
|
|||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.util.Time;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.ws.rs.core.Application;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
@ -350,6 +354,22 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue