new cas auth module
This commit is contained in:
commit
49f0308c4f
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/bin
|
||||||
|
/build
|
||||||
|
/target
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.settings/
|
||||||
|
/cas-overlay/target/
|
||||||
|
/cas-overlay/build/
|
||||||
|
/cas-overlay/.classpath
|
||||||
|
/cas-overlay/.project
|
||||||
|
/cas-overlay/.settings/
|
||||||
|
/hostsharing-auth/target/
|
||||||
|
/hostsharing-auth/build/
|
||||||
|
/hostsharing-auth/.classpath
|
||||||
|
/hostsharing-auth/.project
|
||||||
|
/hostsharing-auth/.settings/
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CAS Module für Hostsharing
|
||||||
|
==========================
|
||||||
|
|
||||||
|
Dieses Modul authentifiziert Username und Passwort gegen die HSAdmin API.
|
||||||
|
|
||||||
|
Dieses Modul wird gebaut und in das lokale Maven-Repository des aktuellen Users instaliert mit dem Befehl
|
||||||
|
|
||||||
|
mvn clean install
|
69
pom.xml
Normal file
69
pom.xml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>net.hostsharing.cas</groupId>
|
||||||
|
<artifactId>casauthhsadmin</artifactId>
|
||||||
|
<version>1.0.4</version>
|
||||||
|
<name>CAS Auth HSAdmin</name>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>11</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<cas.version>6.4.4.2</cas.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apereo.cas</groupId>
|
||||||
|
<artifactId>cas-server-core</artifactId>
|
||||||
|
<version>${cas.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apereo.cas</groupId>
|
||||||
|
<artifactId>cas-server-core-authentication-api</artifactId>
|
||||||
|
<version>${cas.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apereo.cas</groupId>
|
||||||
|
<artifactId>cas-server-core-util-api</artifactId>
|
||||||
|
<version>${cas.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apereo.cas</groupId>
|
||||||
|
<artifactId>cas-server-core-configuration-api</artifactId>
|
||||||
|
<version>${cas.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.xmlrpc</groupId>
|
||||||
|
<artifactId>xmlrpc-client</artifactId>
|
||||||
|
<version>3.1.3</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>xml-apis</groupId>
|
||||||
|
<artifactId>xml-apis</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<compilerArgument></compilerArgument>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<finalName>casauthhsadmin</finalName>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,46 @@
|
|||||||
|
package net.hostsharing.cas.auth;
|
||||||
|
|
||||||
|
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
|
||||||
|
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
|
||||||
|
import org.apereo.cas.authentication.AuthenticationHandler;
|
||||||
|
import org.apereo.cas.authentication.principal.PrincipalFactory;
|
||||||
|
import org.apereo.cas.authentication.principal.PrincipalFactoryUtils;
|
||||||
|
import org.apereo.cas.authentication.principal.PrincipalResolver;
|
||||||
|
import org.apereo.cas.services.ServicesManager;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
public class HostsharingAuthEventExecutionPlanConfiguration implements AuthenticationEventExecutionPlanConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("servicesManager")
|
||||||
|
private ObjectProvider<ServicesManager> servicesManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("defaultPrincipalResolver")
|
||||||
|
private ObjectProvider<PrincipalResolver> defaultPrincipalResolver;
|
||||||
|
|
||||||
|
@ConditionalOnMissingBean(name = "hostsharingAuthenticationPrincipalFactory")
|
||||||
|
@Bean
|
||||||
|
@RefreshScope
|
||||||
|
public PrincipalFactory hostsharingAuthenticationPrincipalFactory() {
|
||||||
|
return PrincipalFactoryUtils.newPrincipalFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationHandler myAuthenticationHandler() {
|
||||||
|
final String name = "Hostsharing Authentication";
|
||||||
|
return new HostsharingAuthenticationHandler(name, servicesManager.getObject(), hostsharingAuthenticationPrincipalFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
|
||||||
|
plan.registerAuthenticationHandler(myAuthenticationHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
package net.hostsharing.cas.auth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import org.apache.xmlrpc.XmlRpcException;
|
||||||
|
import org.apache.xmlrpc.client.XmlRpcClient;
|
||||||
|
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
|
||||||
|
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
|
||||||
|
import org.apereo.cas.authentication.CoreAuthenticationUtils;
|
||||||
|
import org.apereo.cas.authentication.PreventedException;
|
||||||
|
import org.apereo.cas.authentication.credential.UsernamePasswordCredential;
|
||||||
|
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
|
||||||
|
import org.apereo.cas.authentication.principal.Principal;
|
||||||
|
import org.apereo.cas.authentication.principal.PrincipalFactory;
|
||||||
|
import org.apereo.cas.services.ServicesManager;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
public class HostsharingAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
|
||||||
|
|
||||||
|
public HostsharingAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory) {
|
||||||
|
super(name, servicesManager, principalFactory, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword)
|
||||||
|
throws GeneralSecurityException, PreventedException {
|
||||||
|
|
||||||
|
final String username = credential.getUsername();
|
||||||
|
final String password = credential.getPassword();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
final Map<String, List<Object>> attributes = validateCredentials(username, password);
|
||||||
|
final Principal principal = this.principalFactory.createPrincipal(username, attributes);
|
||||||
|
return createHandlerResult(credential, principal);
|
||||||
|
|
||||||
|
} catch (PasswordValidationException | IOException | XmlRpcException | ParserConfigurationException | SAXException e) {
|
||||||
|
throw new GeneralSecurityException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, List<Object>> validateCredentials(final String login, final String password)
|
||||||
|
throws PasswordValidationException, XmlRpcException, GeneralSecurityException, IOException, ParserConfigurationException, SAXException {
|
||||||
|
|
||||||
|
if (!login.contains("@")) {
|
||||||
|
throw new GeneralSecurityException("expect email address");
|
||||||
|
}
|
||||||
|
final String emailDomain = login.split("@")[1];
|
||||||
|
final URL url = new URL("http://" + emailDomain + "/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=" + login);
|
||||||
|
final InputStream autoconfigStream = url.openConnection().getInputStream();
|
||||||
|
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||||
|
final Document document = documentBuilder.parse(autoconfigStream);
|
||||||
|
final NodeList inServersNodes = document.getElementsByTagName("username");
|
||||||
|
if (inServersNodes.getLength() != 2) {
|
||||||
|
throw new GeneralSecurityException("expect email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String username = inServersNodes.item(0).getTextContent();
|
||||||
|
|
||||||
|
|
||||||
|
final TicketService ticketService = new TicketService(username, password);
|
||||||
|
final String grantingTicket = ticketService.getGrantingTicket();
|
||||||
|
final String ticket = ticketService.getServiceTicket(grantingTicket);
|
||||||
|
|
||||||
|
final XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
|
||||||
|
config.setServerURL(new URL("https://config.hostsharing.net:443/hsar/xmlrpc/hsadmin"));
|
||||||
|
config.setEnabledForExtensions(true);
|
||||||
|
final XmlRpcClient client = new XmlRpcClient();
|
||||||
|
client.setConfig(config);
|
||||||
|
|
||||||
|
final List<Serializable> xmlRpcParamsList = new ArrayList<Serializable>();
|
||||||
|
xmlRpcParamsList.add(username);
|
||||||
|
xmlRpcParamsList.add(ticket);
|
||||||
|
final HashMap<String, Serializable> whereParamsMap = new HashMap<String, Serializable>();
|
||||||
|
whereParamsMap.put("name", username);
|
||||||
|
xmlRpcParamsList.add(whereParamsMap);
|
||||||
|
final Object[] rpcResult = (Object[]) client.execute("user.search", xmlRpcParamsList);
|
||||||
|
if (rpcResult.length != 1) {
|
||||||
|
throw new GeneralSecurityException("unknown username");
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<String, Serializable> userData = (Map<String, Serializable>) rpcResult[0];
|
||||||
|
final String comment = (String) userData.get("comment");
|
||||||
|
int firstCommaIndex = comment.indexOf(',');
|
||||||
|
String displayName = comment;
|
||||||
|
String[] groups = new String[0];
|
||||||
|
if (firstCommaIndex > 0) {
|
||||||
|
displayName = comment.substring(0, firstCommaIndex).trim();
|
||||||
|
final String[] splitStrings = comment.substring(firstCommaIndex + 1).split(",");
|
||||||
|
groups = new String[splitStrings.length];
|
||||||
|
for (int idx=0; idx<splitStrings.length; idx++) {
|
||||||
|
groups[idx] = splitStrings[idx].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final Map<String, Object> attribsMap = new HashMap<String, Object>();
|
||||||
|
attribsMap.put("groups", groups);
|
||||||
|
attribsMap.put("displayName", displayName);
|
||||||
|
attribsMap.put("mail", login);
|
||||||
|
final Map<String, List<Object>> attributes = CoreAuthenticationUtils.convertAttributeValuesToMultiValuedObjects(attribsMap);
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
try {
|
||||||
|
Map<String, List<Object>> map = validateCredentials(args[0], args[1]);
|
||||||
|
for (String key : map.keySet()) {
|
||||||
|
System.out.println(key + ": " + map.get(key));
|
||||||
|
}
|
||||||
|
} catch (IOException | PasswordValidationException | XmlRpcException | GeneralSecurityException | ParserConfigurationException | SAXException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.hostsharing.cas.auth;
|
||||||
|
|
||||||
|
public class PasswordValidationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public PasswordValidationException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordValidationException(final Throwable excption) {
|
||||||
|
super(excption);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PasswordValidationException(final String message, final Throwable exception) {
|
||||||
|
super(message, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
85
src/main/java/net/hostsharing/cas/auth/TicketService.java
Normal file
85
src/main/java/net/hostsharing/cas/auth/TicketService.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package net.hostsharing.cas.auth;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for service tickets.
|
||||||
|
* Hostsharing uses the CAS authentication service to authenticate
|
||||||
|
* users of hostsharing services. This class is used to create a
|
||||||
|
* "ticket granting ticket" for a session and service ticket for
|
||||||
|
* individual service calls.
|
||||||
|
*/
|
||||||
|
public class TicketService {
|
||||||
|
final String user;
|
||||||
|
final String password;
|
||||||
|
|
||||||
|
public TicketService(final String user, final String password) {
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGrantingTicket() throws PasswordValidationException {
|
||||||
|
String ticket = null;
|
||||||
|
try {
|
||||||
|
String userParam = "username=" + URLEncoder.encode(user, "UTF-8");
|
||||||
|
String passwordParam = "password=" + URLEncoder.encode(password, "UTF-8");
|
||||||
|
String encodedData = userParam + "&" + passwordParam;
|
||||||
|
URL url = new URL("https://login.hostsharing.net/cas/v1/tickets");
|
||||||
|
|
||||||
|
final HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
|
connection.setDoInput(true);
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
connection.setAllowUserInteraction(false);
|
||||||
|
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
||||||
|
writer.write(encodedData);
|
||||||
|
writer.close();
|
||||||
|
connection.connect();
|
||||||
|
ticket = connection.getHeaderField("Location");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PasswordValidationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceTicket(String grantingTicket) throws PasswordValidationException {
|
||||||
|
String ticket = null;
|
||||||
|
try {
|
||||||
|
String serviceParam = "service=" + URLEncoder.encode("https://config.hostsharing.net:443/hsar/backend", "UTF-8");
|
||||||
|
URL url = new URL(grantingTicket);
|
||||||
|
|
||||||
|
final HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
|
connection.setDoInput(true);
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
connection.setUseCaches(false);
|
||||||
|
connection.setAllowUserInteraction(false);
|
||||||
|
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
||||||
|
writer.write(serviceParam);
|
||||||
|
writer.close();
|
||||||
|
connection.connect();
|
||||||
|
final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
|
ticket = reader.readLine();
|
||||||
|
String readLine = reader.readLine();
|
||||||
|
do {
|
||||||
|
readLine = reader.readLine();
|
||||||
|
} while (readLine != null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PasswordValidationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
1
src/main/resources/META-INF/spring.factories
Normal file
1
src/main/resources/META-INF/spring.factories
Normal file
@ -0,0 +1 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.hostsharing.cas.auth.HostsharingAuthEventExecutionPlanConfiguration
|
Loading…
Reference in New Issue
Block a user