17
0

new cas auth module

This commit is contained in:
Peter Hormanns 2022-03-12 18:27:58 +01:00
commit 49f0308c4f
8 changed files with 373 additions and 0 deletions

16
.gitignore vendored Normal file
View 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
View 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
View 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>

View File

@ -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());
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}

View 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;
}
}

View File

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=net.hostsharing.cas.auth.HostsharingAuthEventExecutionPlanConfiguration