mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-30 11:29:57 -06:00
Add initial IPA-Tuura federation (#35467)
* Add initial federation ipatuura plugin Closes #35325 Signed-off-by: Justin Stephenson <jstephen@redhat.com> Signed-off-by: Stefan Guilhen <sguilhen@redhat.com> Co-authored-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
@@ -125,6 +125,8 @@ public class Profile {
|
||||
CACHE_EMBEDDED_REMOTE_STORE("Support for remote-store in embedded Infinispan caches", Type.EXPERIMENTAL),
|
||||
|
||||
USER_EVENT_METRICS("Collect metrics based on user events", Type.PREVIEW),
|
||||
|
||||
IPA_TUURA_FEDERATION("IPA-Tuura user federation provider", Type.EXPERIMENTAL)
|
||||
;
|
||||
|
||||
private final Type type;
|
||||
|
||||
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
@@ -86,6 +86,10 @@
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-sssd-federation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-ipatuura-federation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Built-in Authorization Policy Providers -->
|
||||
<dependency>
|
||||
|
||||
64
federation/ipatuura/pom.xml
Normal file
64
federation/ipatuura/pom.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
~ Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>999.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-ipatuura-federation</artifactId>
|
||||
<name>Keycloak Ipatuura Federation</name>
|
||||
<description />
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-storage-private</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-services</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp.Response;
|
||||
|
||||
import org.keycloak.ipatuura_user_spi.schemas.SCIMSearchRequest;
|
||||
import org.keycloak.ipatuura_user_spi.schemas.SCIMUser;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
public class Ipatuura {
|
||||
private static final Logger logger = Logger.getLogger(Ipatuura.class);
|
||||
|
||||
private final ComponentModel model;
|
||||
public static final String SCHEMA_CORE_USER = "urn:ietf:params:scim:schemas:core:2.0:User";
|
||||
public static final String SCHEMA_API_MESSAGES_SEARCHREQUEST = "urn:ietf:params:scim:api:messages:2.0:SearchRequest";
|
||||
|
||||
String sessionid_cookie;
|
||||
String csrf_cookie;
|
||||
String csrf_value;
|
||||
Boolean logged_in = false;
|
||||
|
||||
private final KeycloakSession session;
|
||||
|
||||
public Ipatuura(KeycloakSession session, ComponentModel model) {
|
||||
this.model = model;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
private void parseSetCookie(Response response) throws IOException {
|
||||
List<String> setCookieHeaders = response.getHeader("Set-Cookie");
|
||||
|
||||
for (String h : setCookieHeaders) {
|
||||
String[] kv = h.split(";");
|
||||
for (String s : kv) {
|
||||
if (s.contains("csrftoken")) {
|
||||
/* key=value */
|
||||
csrf_cookie = s;
|
||||
csrf_value = s.substring(s.lastIndexOf("=") + 1);
|
||||
} else if (s.contains("sessionid")) {
|
||||
/* key=value */
|
||||
sessionid_cookie = s;
|
||||
csrf_cookie += String.format("; %s", sessionid_cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Integer csrfAuthLogin() {
|
||||
|
||||
Response response;
|
||||
|
||||
/* Get inputs */
|
||||
String server = model.getConfig().getFirst("scimurl");
|
||||
String username = model.getConfig().getFirst("loginusername");
|
||||
String password = model.getConfig().getFirst("loginpassword");
|
||||
|
||||
/* Execute GET to get initial csrftoken */
|
||||
String url = String.format("https://%s%s", server, "/admin/login/");
|
||||
|
||||
try {
|
||||
response = SimpleHttp.doGet(url, session).asResponse();
|
||||
parseSetCookie(response);
|
||||
response.close();
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
/* Perform login POST */
|
||||
try {
|
||||
/* Here we retrieve the Response sessionid and csrftoken cookie */
|
||||
response = SimpleHttp.doPost(url, session).header("X-CSRFToken", csrf_value).header("Cookie", csrf_cookie)
|
||||
.header("referer", url).param("username", username).param("password", password).asResponse();
|
||||
|
||||
parseSetCookie(response);
|
||||
response.close();
|
||||
} catch (Exception e) {
|
||||
logger.error("Error: " + e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
this.logged_in = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isValid(String username, String password) {
|
||||
|
||||
if (!this.logged_in) {
|
||||
this.csrfAuthLogin();
|
||||
}
|
||||
|
||||
/* Build URL */
|
||||
String server = model.getConfig().getFirst("scimurl");
|
||||
String endpointurl = String.format("https://%s/creds/simple_pwd", server);
|
||||
|
||||
logger.debugv("Sending POST request to {0}", endpointurl);
|
||||
SimpleHttp simpleHttp = SimpleHttp.doPost(endpointurl, session).header("X-CSRFToken", this.csrf_value)
|
||||
.header("Cookie", this.csrf_cookie).header("SessionId", sessionid_cookie).header("referer", endpointurl)
|
||||
.param("username", username).param("password", password);
|
||||
try (Response response = simpleHttp.asResponse()){
|
||||
JsonNode result = response.asJson();
|
||||
return (result.get("result").get("validated").asBoolean());
|
||||
} catch (Exception e) {
|
||||
logger.debugv("Failed to authenticate user {0}: {1}", username, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String gssAuth(String spnegoToken) {
|
||||
|
||||
String server = model.getConfig().getFirst("scimurl");
|
||||
String endpointurl = String.format("https://%s/bridge/login_kerberos/", server);
|
||||
|
||||
logger.debugv("Sending POST request to {0}", endpointurl);
|
||||
SimpleHttp simpleHttp = SimpleHttp.doPost(endpointurl, session).header("Authorization", "Negotiate " + spnegoToken)
|
||||
.param("username", "");
|
||||
try (Response response = simpleHttp.asResponse()) {
|
||||
logger.debugv("Response status is {0}", response.getStatus());
|
||||
return response.getFirstHeader("Remote-User");
|
||||
} catch (Exception e) {
|
||||
logger.debugv("Failed to authenticate user with GSSAPI: {0}", e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public <T> Response clientRequest(String endpoint, String method, T entity) throws Exception {
|
||||
Response response = null;
|
||||
|
||||
if (!this.logged_in) {
|
||||
this.csrfAuthLogin();
|
||||
}
|
||||
|
||||
/* Build URL */
|
||||
String server = model.getConfig().getFirst("scimurl");
|
||||
String endpointurl;
|
||||
if (endpoint.contains("domain")) {
|
||||
endpointurl = String.format("https://%s/domains/v1/%s/", server, endpoint);
|
||||
} else {
|
||||
endpointurl = String.format("https://%s/scim/v2/%s", server, endpoint);
|
||||
}
|
||||
|
||||
logger.debugv("Sending {0} request to {1}", method, endpointurl);
|
||||
|
||||
try {
|
||||
switch (method) {
|
||||
case "GET":
|
||||
response = SimpleHttp.doGet(endpointurl, session).header("X-CSRFToken", csrf_value)
|
||||
.header("Cookie", csrf_cookie).header("SessionId", sessionid_cookie).asResponse();
|
||||
break;
|
||||
case "DELETE":
|
||||
response = SimpleHttp.doDelete(endpointurl, session).header("X-CSRFToken", csrf_value)
|
||||
.header("Cookie", csrf_cookie).header("SessionId", sessionid_cookie).header("referer", endpointurl)
|
||||
.asResponse();
|
||||
break;
|
||||
case "POST":
|
||||
/* Header is needed for domains endpoint only, but use it here anyway */
|
||||
response = SimpleHttp.doPost(endpointurl, session).header("X-CSRFToken", this.csrf_value)
|
||||
.header("Cookie", this.csrf_cookie).header("SessionId", sessionid_cookie)
|
||||
.header("referer", endpointurl).json(entity).asResponse();
|
||||
break;
|
||||
case "PUT":
|
||||
response = SimpleHttp.doPut(endpointurl, session).header("X-CSRFToken", this.csrf_value)
|
||||
.header("SessionId", sessionid_cookie).header("Cookie", this.csrf_cookie).json(entity).asResponse();
|
||||
break;
|
||||
default:
|
||||
logger.warn("Unknown HTTP method, skipping");
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
/* Caller is responsible for executing .close() */
|
||||
return response;
|
||||
}
|
||||
|
||||
private SCIMSearchRequest setupSearch(String username, String attribute) {
|
||||
List<String> schemas = new ArrayList<String>();
|
||||
SCIMSearchRequest search = new SCIMSearchRequest();
|
||||
String filter;
|
||||
|
||||
schemas.add(SCHEMA_API_MESSAGES_SEARCHREQUEST);
|
||||
search.setSchemas(schemas);
|
||||
|
||||
filter = String.format("%s eq \"%s\"", attribute, username);
|
||||
search.setFilter(filter);
|
||||
logger.debugv("filter: {0}", filter);
|
||||
logger.debugv("Schema: {0}", SCHEMA_API_MESSAGES_SEARCHREQUEST);
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
private SCIMUser getUserByAttr(String username, String attribute) {
|
||||
SCIMSearchRequest newSearch = setupSearch(username, attribute);
|
||||
|
||||
String usersSearchUrl = "Users/.search";
|
||||
SCIMUser user = null;
|
||||
|
||||
Response response;
|
||||
try {
|
||||
response = clientRequest(usersSearchUrl, "POST", newSearch);
|
||||
user = response.asJson(SCIMUser.class);
|
||||
response.close();
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public SCIMUser getUserByUsername(String username) {
|
||||
String attribute = "userName";
|
||||
return getUserByAttr(username, attribute);
|
||||
}
|
||||
|
||||
public SCIMUser getUserByEmail(String username) {
|
||||
String attribute = "emails.value";
|
||||
return getUserByAttr(username, attribute);
|
||||
}
|
||||
|
||||
public SCIMUser getUserByFirstName(String username) {
|
||||
String attribute = "name.givenName";
|
||||
return getUserByAttr(username, attribute);
|
||||
}
|
||||
|
||||
public SCIMUser getUserByLastName(String username) {
|
||||
String attribute = "name.familyName";
|
||||
return getUserByAttr(username, attribute);
|
||||
}
|
||||
|
||||
public Response deleteUser(String username) {
|
||||
SCIMUser userobj = getUserByUsername(username);
|
||||
SCIMUser.Resource user = userobj.getResources().get(0);
|
||||
|
||||
String userIdUrl = String.format("Users/%s", user.getId());
|
||||
|
||||
Response response;
|
||||
try {
|
||||
response = clientRequest(userIdUrl, "DELETE", null);
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keycloak UserRegistrationProvider addUser() method only provides username as input, here we provide mostly dummy values
|
||||
* which will be replaced by actual user input via appropriate setter methods once in the returned UserModel
|
||||
*/
|
||||
private SCIMUser.Resource setupUser(String username) {
|
||||
SCIMUser.Resource user = new SCIMUser.Resource();
|
||||
SCIMUser.Resource.Name name = new SCIMUser.Resource.Name();
|
||||
SCIMUser.Resource.Email email = new SCIMUser.Resource.Email();
|
||||
List<String> schemas = new ArrayList<String>();
|
||||
List<SCIMUser.Resource.Email> emails = new ArrayList<SCIMUser.Resource.Email>();
|
||||
List<SCIMUser.Resource.Group> groups = new ArrayList<SCIMUser.Resource.Group>();
|
||||
|
||||
schemas.add(SCHEMA_CORE_USER);
|
||||
user.setSchemas(schemas);
|
||||
user.setUserName(username);
|
||||
user.setActive(true);
|
||||
user.setGroups(groups);
|
||||
|
||||
name.setGivenName("dummyfirstname");
|
||||
name.setMiddleName("");
|
||||
name.setFamilyName("dummylastname");
|
||||
user.setName(name);
|
||||
|
||||
email.setPrimary(true);
|
||||
email.setType("work");
|
||||
email.setValue("dummy@example.com");
|
||||
emails.add(email);
|
||||
user.setEmails(emails);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public Response createUser(String username) {
|
||||
String usersUrl = "Users";
|
||||
|
||||
SCIMUser.Resource newUser = setupUser(username);
|
||||
|
||||
Response response;
|
||||
try {
|
||||
response = clientRequest(usersUrl, "POST", newUser);
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void setUserAttr(SCIMUser.Resource user, String attr, String value) {
|
||||
SCIMUser.Resource.Name name = user.getName();
|
||||
SCIMUser.Resource.Email email = new SCIMUser.Resource.Email();
|
||||
List<SCIMUser.Resource.Email> emails = new ArrayList<SCIMUser.Resource.Email>();
|
||||
|
||||
switch (attr) {
|
||||
case UserModel.FIRST_NAME:
|
||||
name.setGivenName(value);
|
||||
user.setName(name);
|
||||
break;
|
||||
case UserModel.LAST_NAME:
|
||||
name.setFamilyName(value);
|
||||
user.setName(name);
|
||||
break;
|
||||
case UserModel.EMAIL:
|
||||
email.setValue(value);
|
||||
emails.add(email);
|
||||
user.setEmails(emails);
|
||||
break;
|
||||
case UserModel.USERNAME:
|
||||
/* Changing username not supported */
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown user attribute to set: " + attr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Response updateUser(Ipatuura ipatuura, String username, String attr, List<String> values) {
|
||||
logger.debug(String.format("Updating %s attribute for %s", attr, username));
|
||||
/* Get existing user */
|
||||
if (ipatuura.csrfAuthLogin() == null) {
|
||||
logger.error("Error during login");
|
||||
}
|
||||
|
||||
SCIMUser userobj = getUserByUsername(username);
|
||||
SCIMUser.Resource user = userobj.getResources().get(0);
|
||||
|
||||
/* Modify attributes */
|
||||
setUserAttr(user, attr, values.get(0));
|
||||
|
||||
/* Update user in SCIM */
|
||||
String modifyUrl = String.format("Users/%s", user.getId());
|
||||
|
||||
Response response;
|
||||
try {
|
||||
response = clientRequest(modifyUrl, "PUT", user);
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public boolean getActive(SCIMUser user) {
|
||||
return user.getResources().get(0).getActive();
|
||||
}
|
||||
|
||||
public String getEmail(SCIMUser user) {
|
||||
return user.getResources().get(0).getEmails().get(0).getValue();
|
||||
}
|
||||
|
||||
public String getFirstName(SCIMUser user) {
|
||||
return user.getResources().get(0).getName().getGivenName();
|
||||
}
|
||||
|
||||
public String getLastName(SCIMUser user) {
|
||||
return user.getResources().get(0).getName().getFamilyName();
|
||||
}
|
||||
|
||||
public String getUserName(SCIMUser user) {
|
||||
return user.getResources().get(0).getUserName();
|
||||
}
|
||||
|
||||
public String getId(SCIMUser user) {
|
||||
return user.getResources().get(0).getId();
|
||||
}
|
||||
|
||||
public List<String> getGroupsList(SCIMUser user) {
|
||||
List<SCIMUser.Resource.Group> groups = user.getResources().get(0).getGroups();
|
||||
List<String> groupnames = new ArrayList<String>();
|
||||
|
||||
for (SCIMUser.Resource.Group group : groups) {
|
||||
logger.debug("Retrieving group: " + group.getDisplay());
|
||||
groupnames.add(group.getDisplay());
|
||||
}
|
||||
|
||||
return groupnames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
public class IpatuuraUserModelDelegate extends UserModelDelegate {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IpatuuraUserModelDelegate.class);
|
||||
|
||||
private ComponentModel model;
|
||||
|
||||
private final Ipatuura ipatuura;
|
||||
|
||||
public IpatuuraUserModelDelegate(Ipatuura ipatuura, UserModel delegate, ComponentModel model) {
|
||||
super(delegate);
|
||||
this.model = model;
|
||||
this.ipatuura = ipatuura;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String attr, List<String> values) {
|
||||
SimpleHttp.Response resp = this.ipatuura.updateUser(ipatuura, this.getUsername(), attr, values);
|
||||
try {
|
||||
if (resp.getStatus() != HttpStatus.SC_OK && resp.getStatus() != HttpStatus.SC_NO_CONTENT) {
|
||||
logger.warn("Unexpected PUT status code returned");
|
||||
resp.close();
|
||||
return;
|
||||
}
|
||||
resp.close();
|
||||
} catch (IOException e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
super.setAttribute(attr, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
super.setSingleAttribute(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsername(String username) {
|
||||
super.setUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastName(String lastName) {
|
||||
super.setLastName(lastName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFirstName(String first) {
|
||||
super.setFirstName(first);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEmail(String email) {
|
||||
super.setFirstName(email);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.credential.CredentialAuthentication;
|
||||
import org.keycloak.credential.CredentialInput;
|
||||
import org.keycloak.credential.CredentialInputValidator;
|
||||
import org.keycloak.credential.UserCredentialManager;
|
||||
import org.keycloak.models.CredentialValidationOutput;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.PasswordCredentialModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.UserStoragePrivateUtil;
|
||||
import org.keycloak.storage.user.ImportedUserValidation;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
|
||||
import org.keycloak.ipatuura_user_spi.authenticator.IpatuuraAuthenticator;
|
||||
import org.keycloak.ipatuura_user_spi.schemas.SCIMError;
|
||||
import org.keycloak.ipatuura_user_spi.schemas.SCIMUser;
|
||||
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:jstephen@redhat.com">Justin Stephenson</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class IpatuuraUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator,
|
||||
CredentialAuthentication, UserRegistrationProvider, UserQueryProvider, ImportedUserValidation {
|
||||
protected KeycloakSession session;
|
||||
protected ComponentModel model;
|
||||
protected Ipatuura ipatuura;
|
||||
private static final Logger logger = Logger.getLogger(IpatuuraUserStorageProvider.class);
|
||||
protected final Set<String> supportedCredentialTypes = new HashSet<>();
|
||||
protected IpatuuraUserStorageProviderFactory factory;
|
||||
|
||||
public IpatuuraUserStorageProvider(KeycloakSession session, ComponentModel model, Ipatuura ipatuura,
|
||||
IpatuuraUserStorageProviderFactory factory) {
|
||||
this.session = session;
|
||||
this.model = model;
|
||||
this.ipatuura = ipatuura;
|
||||
this.factory = factory;
|
||||
|
||||
supportedCredentialTypes.add(PasswordCredentialModel.TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByEmail(RealmModel realm, String email) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserById(RealmModel realm, String id) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
String username = storageId.getExternalId();
|
||||
return getUserByUsername(realm, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(RealmModel realm, String username) {
|
||||
/*
|
||||
* Remove @realm, this is needed as GSSAPI auth users reach here as user@realm
|
||||
*/
|
||||
int idx = username.indexOf("@");
|
||||
if (idx != -1) {
|
||||
username = username.substring(0, idx);
|
||||
}
|
||||
|
||||
UserModel user = UserStoragePrivateUtil.userLocalStorage(session).getUserByUsername(realm, username);
|
||||
if (user != null) {
|
||||
logger.debug("User already exists in keycloak");
|
||||
return user;
|
||||
} else {
|
||||
return createUserInKeycloak(realm, username);
|
||||
}
|
||||
}
|
||||
|
||||
protected UserModel createUserInKeycloak(RealmModel realm, String username) {
|
||||
SCIMUser scimuser = ipatuura.getUserByUsername(username);
|
||||
if (scimuser.getTotalResults() == 0) {
|
||||
return null;
|
||||
}
|
||||
UserModel user = UserStoragePrivateUtil.userLocalStorage(session).addUser(realm, username);
|
||||
user.setEmail(ipatuura.getEmail(scimuser));
|
||||
user.setFirstName(ipatuura.getFirstName(scimuser));
|
||||
user.setLastName(ipatuura.getLastName(scimuser));
|
||||
user.setFederationLink(model.getId());
|
||||
user.setEnabled(ipatuura.getActive(scimuser));
|
||||
|
||||
for (String name : ipatuura.getGroupsList(scimuser)) {
|
||||
Stream<GroupModel> groupsStream = session.groups().searchForGroupByNameStream(realm, name, false, null, null);
|
||||
GroupModel group = groupsStream.findFirst().orElse(null);
|
||||
|
||||
if (group == null) {
|
||||
logger.debugv("No group found, creating group: {0}", name);
|
||||
group = session.groups().createGroup(realm, name);
|
||||
}
|
||||
user.joinGroup(group);
|
||||
}
|
||||
|
||||
logger.debugv("Creating SCIM user {0} in keycloak", username);
|
||||
return new IpatuuraUserModelDelegate(ipatuura, user, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
public Set<String> getSupportedCredentialTypes() {
|
||||
return new HashSet<String>(this.supportedCredentialTypes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
|
||||
return getSupportedCredentialTypes().contains(credentialType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialType(String credentialType) {
|
||||
return getSupportedCredentialTypes().contains(credentialType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
|
||||
if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* The password can either be validated locally in keycloak (tried first) or in the SCIM server
|
||||
*/
|
||||
if (((UserCredentialManager) user.credentialManager()).isConfiguredLocally(input.getType())) {
|
||||
logger.debugv("Local password validation for {0}", user.getUsername());
|
||||
/* return false in order to fallback to the next validator */
|
||||
return false;
|
||||
} else {
|
||||
logger.debugv("Delegated password validation for {0}", user.getUsername());
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
return ipatuura.isValid(user.getUsername(), input.getChallengeResponse());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel validate(RealmModel realm, UserModel local) {
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
|
||||
SCIMUser scimuser = ipatuura.getUserByUsername(local.getUsername());
|
||||
String fname = ipatuura.getFirstName(scimuser);
|
||||
String lname = ipatuura.getLastName(scimuser);
|
||||
String email = ipatuura.getEmail(scimuser);
|
||||
|
||||
if (!local.getFirstName().equals(fname)) {
|
||||
local.setFirstName(fname);
|
||||
}
|
||||
if (!local.getLastName().equals(lname)) {
|
||||
local.setLastName(lname);
|
||||
}
|
||||
if (!local.getEmail().equals(email)) {
|
||||
local.setEmail(email);
|
||||
}
|
||||
|
||||
return new IpatuuraUserModelDelegate(this.ipatuura, local, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel addUser(RealmModel realm, String username) {
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
|
||||
SimpleHttp.Response resp = ipatuura.createUser(username);
|
||||
|
||||
try {
|
||||
if (resp.getStatus() != HttpStatus.SC_CREATED) {
|
||||
logger.warn("Unexpected create status code returned");
|
||||
SCIMError error = resp.asJson(SCIMError.class);
|
||||
logger.warn(error.getDetail());
|
||||
resp.close();
|
||||
return null;
|
||||
}
|
||||
resp.close();
|
||||
} catch (IOException e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return createUserInKeycloak(realm, username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeUser(RealmModel realm, UserModel user) {
|
||||
logger.debugv("Removing user: {0}", user.getUsername());
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
|
||||
SimpleHttp.Response resp = ipatuura.deleteUser(user.getUsername());
|
||||
Boolean status = false;
|
||||
try {
|
||||
status = resp.getStatus() == HttpStatus.SC_NO_CONTENT;
|
||||
resp.close();
|
||||
} catch (IOException e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private Stream<UserModel> performSearch(RealmModel realm, String search) {
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
|
||||
SCIMUser scimuser = ipatuura.getUserByUsername(search);
|
||||
if (scimuser.getTotalResults() > 0) {
|
||||
logger.debug("User found by username!");
|
||||
if (UserStoragePrivateUtil.userLocalStorage(session).getUserByUsername(realm, search) == null) {
|
||||
UserModel user = getUserByUsername(realm, ipatuura.getUserName(scimuser));
|
||||
users.add(user);
|
||||
} else {
|
||||
logger.debug("User exists!");
|
||||
}
|
||||
|
||||
return users.stream();
|
||||
}
|
||||
|
||||
return users.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> getGroupMembersStream(RealmModel arg0, GroupModel arg1, Integer arg2, Integer arg3) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
Ipatuura ipatuura = this.ipatuura;
|
||||
|
||||
SCIMUser user = null;
|
||||
SimpleHttp.Response response;
|
||||
try {
|
||||
response = ipatuura.clientRequest("/Users", "GET", null);
|
||||
user = response.asJson(SCIMUser.class);
|
||||
response.close();
|
||||
} catch (Exception e) {
|
||||
logger.errorv("Error: {0}", e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return user.getTotalResults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserByUserAttributeStream(RealmModel realm, String attrName, String attrValue) {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<UserModel> searchForUserStream(RealmModel realm, Map<String, String> params, Integer firstResult,
|
||||
Integer maxResults) {
|
||||
String search = params.get(UserModel.SEARCH);
|
||||
/* only supports searching by username */
|
||||
if (search == null)
|
||||
return Stream.empty();
|
||||
return performSearch(realm, search);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCredentialAuthenticationFor(String type) {
|
||||
return UserCredentialModel.KERBEROS.equals(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CredentialValidationOutput authenticate(RealmModel realm, CredentialInput input) {
|
||||
Map<String, String> state = new HashMap<>();
|
||||
String username = null;
|
||||
IpatuuraAuthenticator ipatuuraAuthenticator = factory.createSCIMAuthenticator();
|
||||
|
||||
String token = ipatuuraAuthenticator.getToken(session);
|
||||
if (token != null) {
|
||||
username = ipatuura.gssAuth(token);
|
||||
|
||||
/* Remove realm */
|
||||
int idx = username.indexOf("@");
|
||||
if (idx != -1) {
|
||||
username = username.substring(0, idx);
|
||||
}
|
||||
logger.debug("GSSAPI authenticating with user " + username);
|
||||
}
|
||||
|
||||
UserModel user = getUserByUsername(realm, username);
|
||||
if (user == null) {
|
||||
logger.debug("CredentialValidationOutput failed");
|
||||
return CredentialValidationOutput.failed();
|
||||
}
|
||||
logger.debug("CredentialValidationOutput success!");
|
||||
return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.component.ComponentValidationException;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.storage.UserStorageProviderFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.ipatuura_user_spi.authenticator.IpatuuraAuthenticator;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:jstephen@redhat.com">Justin Stephenson</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class IpatuuraUserStorageProviderFactory implements UserStorageProviderFactory<IpatuuraUserStorageProvider>, EnvironmentDependentProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IpatuuraUserStorageProviderFactory.class);
|
||||
public static final String PROVIDER_NAME = "ipatuura";
|
||||
protected static final List<String> PROVIDERS = new LinkedList<>();
|
||||
protected static final List<ProviderConfigProperty> configMetadata;
|
||||
|
||||
static {
|
||||
PROVIDERS.add("ipa");
|
||||
PROVIDERS.add("ad");
|
||||
PROVIDERS.add("ldap");
|
||||
|
||||
configMetadata = ProviderConfigurationBuilder.create()
|
||||
/* SCIMv2 server url */
|
||||
.property().name("scimurl").type(ProviderConfigProperty.STRING_TYPE).label("Ipatuura Server URL")
|
||||
.helpText("Backend ipatuura server URL in the format: server.example.com:8080").add()
|
||||
/* Login username, used to auth to make HTTP requests */
|
||||
.property().name("loginusername").type(ProviderConfigProperty.STRING_TYPE).label("Login username")
|
||||
.helpText("username to authenticate through the login page").add()
|
||||
/* Login password, used to auth to make HTTP requests */
|
||||
.property().name("loginpassword").type(ProviderConfigProperty.STRING_TYPE).label("Login password")
|
||||
.helpText("password to authenticate through the login page").add().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigProperties() {
|
||||
return configMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config)
|
||||
throws ComponentValidationException {
|
||||
Ipatuura ipatuura = new Ipatuura(session, config);
|
||||
|
||||
SimpleHttp.Response response;
|
||||
|
||||
try {
|
||||
response = ipatuura.clientRequest("", "GET", null);
|
||||
response.close();
|
||||
} catch (Exception e) {
|
||||
throw new ComponentValidationException("Cannot connect to provided URL!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IpatuuraUserStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
Ipatuura ipatuura = new Ipatuura(session, model);
|
||||
return new IpatuuraUserStorageProvider(session, model, ipatuura, this);
|
||||
}
|
||||
|
||||
protected IpatuuraAuthenticator createSCIMAuthenticator() {
|
||||
return new IpatuuraAuthenticator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported(Config.Scope config) {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.IPA_TUURA_FEDERATION);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi.authenticator;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.constants.KerberosConstants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:jstephen@redhat.com.com">Justin Stephenson</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class IpatuuraAuthenticator {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(IpatuuraAuthenticator.class);
|
||||
|
||||
public String getToken(KeycloakSession session) {
|
||||
HttpHeaders headers = session.getContext().getHttpRequest().getHttpHeaders();
|
||||
|
||||
String authHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
|
||||
if (authHeader == null) {
|
||||
logger.debug("authHeader == NULL()!");
|
||||
return null;
|
||||
}
|
||||
String[] tokens = authHeader.split(" ");
|
||||
|
||||
if (tokens.length == 0) { // assume not supported
|
||||
logger.debug("Invalid length of tokens: " + tokens.length);
|
||||
return null;
|
||||
}
|
||||
if (!KerberosConstants.NEGOTIATE.equalsIgnoreCase(tokens[0])) {
|
||||
logger.debug("Unknown scheme " + tokens[0]);
|
||||
return null;
|
||||
}
|
||||
if (tokens.length != 2) {
|
||||
logger.debug("Invalid credentials tokens.length != 2");
|
||||
return null;
|
||||
}
|
||||
|
||||
String spnegoToken = tokens[1];
|
||||
|
||||
return spnegoToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi.schemas;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "schemas", "detail", "status" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public class SCIMError {
|
||||
|
||||
@JsonProperty("schemas")
|
||||
private List<String> schemas = null;
|
||||
@JsonProperty("detail")
|
||||
private String detail;
|
||||
@JsonProperty("status")
|
||||
private Integer status;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public List<String> getSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public void setSchemas(List<String> schemas) {
|
||||
this.schemas = schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("detail")
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
@JsonProperty("detail")
|
||||
public void setDetail(String detail) {
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
@JsonProperty("status")
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@JsonProperty("status")
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi.schemas;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "schemas", "filter" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public class SCIMSearchRequest {
|
||||
|
||||
@JsonProperty("schemas")
|
||||
private List<String> schemas = null;
|
||||
@JsonProperty("filter")
|
||||
private String filter;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public List<String> getSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public void setSchemas(List<String> schemas) {
|
||||
this.schemas = schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("filter")
|
||||
public String getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
@JsonProperty("filter")
|
||||
public void setFilter(String filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.ipatuura_user_spi.schemas;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Generated;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "Resources", "itemsPerPage", "schemas", "startIndex", "totalResults" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public class SCIMUser {
|
||||
|
||||
@JsonProperty("Resources")
|
||||
private List<Resource> resources = null;
|
||||
@JsonProperty("itemsPerPage")
|
||||
private Integer itemsPerPage;
|
||||
@JsonProperty("schemas")
|
||||
private List<String> schemas = null;
|
||||
@JsonProperty("startIndex")
|
||||
private Integer startIndex;
|
||||
@JsonProperty("totalResults")
|
||||
private Integer totalResults;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("Resources")
|
||||
public List<Resource> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
@JsonProperty("Resources")
|
||||
public void setResources(List<Resource> resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@JsonProperty("itemsPerPage")
|
||||
public Integer getItemsPerPage() {
|
||||
return itemsPerPage;
|
||||
}
|
||||
|
||||
@JsonProperty("itemsPerPage")
|
||||
public void setItemsPerPage(Integer itemsPerPage) {
|
||||
this.itemsPerPage = itemsPerPage;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public List<String> getSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public void setSchemas(List<String> schemas) {
|
||||
this.schemas = schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("startIndex")
|
||||
public Integer getStartIndex() {
|
||||
return startIndex;
|
||||
}
|
||||
|
||||
@JsonProperty("startIndex")
|
||||
public void setStartIndex(Integer startIndex) {
|
||||
this.startIndex = startIndex;
|
||||
}
|
||||
|
||||
@JsonProperty("totalResults")
|
||||
public Integer getTotalResults() {
|
||||
return totalResults;
|
||||
}
|
||||
|
||||
@JsonProperty("totalResults")
|
||||
public void setTotalResults(Integer totalResults) {
|
||||
this.totalResults = totalResults;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "active", "emails", "groups", "id", "meta", "name", "schemas", "userName" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public static class Resource {
|
||||
|
||||
@JsonProperty("active")
|
||||
private Boolean active;
|
||||
@JsonProperty("emails")
|
||||
private List<Email> emails = null;
|
||||
@JsonProperty("groups")
|
||||
private List<Group> groups = null;
|
||||
@JsonProperty("id")
|
||||
private String id;
|
||||
@JsonProperty("meta")
|
||||
private Meta meta;
|
||||
@JsonProperty("name")
|
||||
private Name name;
|
||||
@JsonProperty("schemas")
|
||||
private List<String> schemas = null;
|
||||
@JsonProperty("userName")
|
||||
private String userName;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("active")
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@JsonProperty("active")
|
||||
public void setActive(boolean b) {
|
||||
this.active = b;
|
||||
}
|
||||
|
||||
@JsonProperty("emails")
|
||||
public List<Email> getEmails() {
|
||||
return emails;
|
||||
}
|
||||
|
||||
@JsonProperty("emails")
|
||||
public void setEmails(List<Email> emails) {
|
||||
this.emails = emails;
|
||||
}
|
||||
|
||||
@JsonProperty("groups")
|
||||
public List<Group> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
|
||||
@JsonProperty("groups")
|
||||
public void setGroups(List<Group> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@JsonProperty("id")
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@JsonProperty("id")
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@JsonProperty("meta")
|
||||
public Meta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
@JsonProperty("meta")
|
||||
public void setMeta(Meta meta) {
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
@JsonProperty("name")
|
||||
public Name getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@JsonProperty("name")
|
||||
public void setName(Name name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public List<String> getSchemas() {
|
||||
return schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("schemas")
|
||||
public void setSchemas(List<String> schemas) {
|
||||
this.schemas = schemas;
|
||||
}
|
||||
|
||||
@JsonProperty("userName")
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
@JsonProperty("userName")
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "familyName", "givenName", "middleName" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public static class Name {
|
||||
|
||||
@JsonProperty("familyName")
|
||||
private String familyName;
|
||||
@JsonProperty("givenName")
|
||||
private String givenName;
|
||||
@JsonProperty("middleName")
|
||||
private String middleName;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("familyName")
|
||||
public String getFamilyName() {
|
||||
return familyName;
|
||||
}
|
||||
|
||||
@JsonProperty("familyName")
|
||||
public void setFamilyName(String familyName) {
|
||||
this.familyName = familyName;
|
||||
}
|
||||
|
||||
@JsonProperty("givenName")
|
||||
public String getGivenName() {
|
||||
return givenName;
|
||||
}
|
||||
|
||||
@JsonProperty("givenName")
|
||||
public void setGivenName(String givenName) {
|
||||
this.givenName = givenName;
|
||||
}
|
||||
|
||||
@JsonProperty("middleName")
|
||||
public String getMiddleName() {
|
||||
return middleName;
|
||||
}
|
||||
|
||||
@JsonProperty("middleName")
|
||||
public void setMiddleName(String middleName) {
|
||||
this.middleName = middleName;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "location", "resourceType" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public static class Meta {
|
||||
|
||||
@JsonProperty("location")
|
||||
private String location;
|
||||
@JsonProperty("resourceType")
|
||||
private String resourceType;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("location")
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@JsonProperty("location")
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@JsonProperty("resourceType")
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
@JsonProperty("resourceType")
|
||||
public void setResourceType(String resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "primary", "type", "value" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public static class Email {
|
||||
|
||||
@JsonProperty("primary")
|
||||
private Boolean primary;
|
||||
@JsonProperty("type")
|
||||
private String type;
|
||||
@JsonProperty("value")
|
||||
private String value;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("primary")
|
||||
public Boolean getPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
@JsonProperty("primary")
|
||||
public void setPrimary(Boolean primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
@JsonProperty("type")
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@JsonProperty("type")
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@JsonProperty("value")
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@JsonProperty("value")
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JsonPropertyOrder({ "$ref", "display", "value" })
|
||||
@Generated("jsonschema2pojo")
|
||||
public static class Group {
|
||||
|
||||
@JsonProperty("$ref")
|
||||
private String $ref;
|
||||
@JsonProperty("display")
|
||||
private String display;
|
||||
@JsonProperty("value")
|
||||
private String value;
|
||||
@JsonIgnore
|
||||
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
|
||||
|
||||
@JsonProperty("$ref")
|
||||
public String get$ref() {
|
||||
return $ref;
|
||||
}
|
||||
|
||||
@JsonProperty("$ref")
|
||||
public void set$ref(String $ref) {
|
||||
this.$ref = $ref;
|
||||
}
|
||||
|
||||
@JsonProperty("display")
|
||||
public String getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
@JsonProperty("display")
|
||||
public void setDisplay(String display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
@JsonProperty("value")
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@JsonProperty("value")
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAdditionalProperties() {
|
||||
return this.additionalProperties;
|
||||
}
|
||||
|
||||
@JsonAnySetter
|
||||
public void setAdditionalProperty(String name, Object value) {
|
||||
this.additionalProperties.put(name, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.ipatuura_user_spi.IpatuuraUserStorageProviderFactory
|
||||
@@ -36,6 +36,7 @@
|
||||
<module>kerberos</module>
|
||||
<module>ldap</module>
|
||||
<module>sssd</module>
|
||||
<module>ipatuura</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
5
pom.xml
5
pom.xml
@@ -917,6 +917,11 @@
|
||||
<artifactId>keycloak-ldap-federation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-ipatuura-federation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-dependencies-server-min</artifactId>
|
||||
|
||||
@@ -334,6 +334,16 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-ipatuura-federation</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-config-api</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user