mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 09:03:21 +00:00 
			
		
		
		
	GUACAMOLE-1020: Implement extension with enhanced login and connection restrictions.
This commit is contained in:
		
							
								
								
									
										3
									
								
								extensions/guacamole-auth-restrict/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								extensions/guacamole-auth-restrict/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| src/main/resources/generated/ | ||||
| target/ | ||||
| *~ | ||||
							
								
								
									
										0
									
								
								extensions/guacamole-auth-restrict/.ratignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								extensions/guacamole-auth-restrict/.ratignore
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										179
									
								
								extensions/guacamole-auth-restrict/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								extensions/guacamole-auth-restrict/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
|     Licensed to the Apache Software Foundation (ASF) under one | ||||
|     or more contributor license agreements.  See the NOTICE file | ||||
|     distributed with this work for additional information | ||||
|     regarding copyright ownership.  The ASF licenses this file | ||||
|     to you 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/maven-v4_0_0.xsd"> | ||||
|  | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <groupId>org.apache.guacamole</groupId> | ||||
|     <artifactId>guacamole-auth-restrict</artifactId> | ||||
|     <packaging>jar</packaging> | ||||
|     <version>1.6.0</version> | ||||
|     <name>guacamole-auth-restrict</name> | ||||
|     <url>http://guacamole.apache.org/</url> | ||||
|  | ||||
|     <parent> | ||||
|         <groupId>org.apache.guacamole</groupId> | ||||
|         <artifactId>extensions</artifactId> | ||||
|         <version>1.6.0</version> | ||||
|         <relativePath>../</relativePath> | ||||
|     </parent> | ||||
|  | ||||
|     <build> | ||||
|         <plugins> | ||||
|  | ||||
|             <!-- Pre-cache Angular templates with maven-angular-plugin --> | ||||
|             <plugin> | ||||
|                 <groupId>com.keithbranton.mojo</groupId> | ||||
|                 <artifactId>angular-maven-plugin</artifactId> | ||||
|                 <version>0.3.4</version> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <phase>generate-resources</phase> | ||||
|                         <goals> | ||||
|                             <goal>html2js</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|                 <configuration> | ||||
|                     <sourceDir>${basedir}/src/main/resources</sourceDir> | ||||
|                     <include>**/*.html</include> | ||||
|                     <target>${basedir}/src/main/resources/generated/templates-main/templates.js</target> | ||||
|                     <prefix>app/ext/restrict</prefix> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|  | ||||
|             <!-- JS/CSS Minification Plugin --> | ||||
|             <plugin> | ||||
|                 <groupId>com.github.buckelieg</groupId> | ||||
|                 <artifactId>minify-maven-plugin</artifactId> | ||||
|                 <executions> | ||||
|                     <execution> | ||||
|                         <id>default-cli</id> | ||||
|                         <configuration> | ||||
|                             <charset>UTF-8</charset> | ||||
|  | ||||
|                             <webappSourceDir>${basedir}/src/main/resources</webappSourceDir> | ||||
|                             <webappTargetDir>${project.build.directory}/classes</webappTargetDir> | ||||
|  | ||||
|                             <cssSourceDir>/</cssSourceDir> | ||||
|                             <cssTargetDir>/</cssTargetDir> | ||||
|                             <cssFinalFile>restrict.css</cssFinalFile> | ||||
|  | ||||
|                             <cssSourceFiles> | ||||
|                                 <cssSourceFile>license.txt</cssSourceFile> | ||||
|                             </cssSourceFiles> | ||||
|  | ||||
|                             <cssSourceIncludes> | ||||
|                                 <cssSourceInclude>**/*.css</cssSourceInclude> | ||||
|                             </cssSourceIncludes> | ||||
|  | ||||
|                             <jsSourceDir>/</jsSourceDir> | ||||
|                             <jsTargetDir>/</jsTargetDir> | ||||
|                             <jsFinalFile>restrict.js</jsFinalFile> | ||||
|  | ||||
|                             <jsSourceFiles> | ||||
|                                 <jsSourceFile>license.txt</jsSourceFile> | ||||
|                             </jsSourceFiles> | ||||
|  | ||||
|                             <jsSourceIncludes> | ||||
|                                 <jsSourceInclude>**/*.js</jsSourceInclude> | ||||
|                             </jsSourceIncludes> | ||||
|  | ||||
|                             <!-- Do not minify and include tests --> | ||||
|                             <jsSourceExcludes> | ||||
|                                 <jsSourceExclude>**/*.test.js</jsSourceExclude> | ||||
|                             </jsSourceExcludes> | ||||
|                             <jsEngine>CLOSURE</jsEngine> | ||||
|  | ||||
|                             <!-- Disable warnings for JSDoc annotations --> | ||||
|                             <closureWarningLevels> | ||||
|                                 <misplacedTypeAnnotation>OFF</misplacedTypeAnnotation> | ||||
|                                 <nonStandardJsDocs>OFF</nonStandardJsDocs> | ||||
|                             </closureWarningLevels> | ||||
|  | ||||
|                         </configuration> | ||||
|                         <goals> | ||||
|                             <goal>minify</goal> | ||||
|                         </goals> | ||||
|                     </execution> | ||||
|                 </executions> | ||||
|             </plugin> | ||||
|  | ||||
|         </plugins> | ||||
|     </build> | ||||
|  | ||||
|     <dependencies> | ||||
|  | ||||
|        <!-- Guacamole Extension API --> | ||||
|         <dependency> | ||||
|             <groupId>org.apache.guacamole</groupId> | ||||
|             <artifactId>guacamole-ext</artifactId> | ||||
|             <version>1.6.0</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|          | ||||
|         <!-- Guava - Utility Library --> | ||||
|         <dependency> | ||||
|             <groupId>com.google.guava</groupId> | ||||
|             <artifactId>guava</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Guice --> | ||||
|         <dependency> | ||||
|             <groupId>com.google.inject</groupId> | ||||
|             <artifactId>guice</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Java servlet API --> | ||||
|         <dependency> | ||||
|             <groupId>javax.servlet</groupId> | ||||
|             <artifactId>servlet-api</artifactId> | ||||
|             <version>2.5</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- JUnit --> | ||||
|         <dependency> | ||||
|             <groupId>junit</groupId> | ||||
|             <artifactId>junit</artifactId> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Guacamole depends on an implementation of JAX-WS --> | ||||
|         <dependency> | ||||
|             <groupId>javax.ws.rs</groupId> | ||||
|             <artifactId>javax.ws.rs-api</artifactId> | ||||
|             <version>2.0</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|          | ||||
|         <!-- Library for unified IPv4/6 parsing and validation --> | ||||
|         <dependency> | ||||
|             <groupId>com.github.seancfoley</groupId> | ||||
|             <artifactId>ipaddress</artifactId> | ||||
|             <version>5.5.0</version> | ||||
|             <scope>provided</scope> | ||||
|         </dependency> | ||||
|  | ||||
|     </dependencies> | ||||
|  | ||||
| </project> | ||||
| @@ -0,0 +1,53 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <!-- | ||||
|     Licensed to the Apache Software Foundation (ASF) under one | ||||
|     or more contributor license agreements.  See the NOTICE file | ||||
|     distributed with this work for additional information | ||||
|     regarding copyright ownership.  The ASF licenses this file | ||||
|     to you 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. | ||||
| --> | ||||
| <assembly | ||||
|     xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" | ||||
|     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|     xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> | ||||
|      | ||||
|     <id>dist</id> | ||||
|     <baseDirectory>${project.artifactId}-${project.version}</baseDirectory> | ||||
|  | ||||
|     <!-- Output tar.gz --> | ||||
|     <formats> | ||||
|         <format>tar.gz</format> | ||||
|     </formats> | ||||
|  | ||||
|     <!-- Include licenses and extension .jar --> | ||||
|     <fileSets> | ||||
|  | ||||
|         <!-- Include licenses --> | ||||
|         <fileSet> | ||||
|             <outputDirectory></outputDirectory> | ||||
|             <directory>target/licenses</directory> | ||||
|         </fileSet> | ||||
|  | ||||
|         <!-- Include extension .jar --> | ||||
|         <fileSet> | ||||
|             <directory>target</directory> | ||||
|             <outputDirectory></outputDirectory> | ||||
|             <includes> | ||||
|                 <include>*.jar</include> | ||||
|             </includes> | ||||
|         </fileSet> | ||||
|  | ||||
|     </fileSets> | ||||
|  | ||||
| </assembly> | ||||
| @@ -0,0 +1,62 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.restrict.user.RestrictUserContext; | ||||
| import org.apache.guacamole.net.auth.AbstractAuthenticationProvider; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| import org.apache.guacamole.net.auth.UserContext; | ||||
|  | ||||
| /** | ||||
|  * AuthenticationProvider implementation which provides additional restrictions | ||||
|  * for users, groups of users, connections, and connection groups, allowing | ||||
|  * administrators to further control access to Guacamole resources. | ||||
|  */ | ||||
| public class RestrictionAuthenticationProvider extends AbstractAuthenticationProvider { | ||||
|  | ||||
|     @Override | ||||
|     public String getIdentifier() { | ||||
|         return "restrict"; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserContext decorate(UserContext context, | ||||
|             AuthenticatedUser authenticatedUser, Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|  | ||||
|         // Verify identity of user | ||||
|         RestrictionVerificationService.verifyLoginRestrictions(context, authenticatedUser); | ||||
|  | ||||
|         // User has been verified, and authentication should be allowed to | ||||
|         // continue | ||||
|         return new RestrictUserContext(context, credentials.getRemoteAddress()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UserContext redecorate(UserContext decorated, UserContext context, | ||||
|             AuthenticatedUser authenticatedUser, Credentials credentials) | ||||
|             throws GuacamoleException { | ||||
|         return new RestrictUserContext(context, credentials.getRemoteAddress()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,333 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import inet.ipaddr.HostName; | ||||
| import inet.ipaddr.HostNameException; | ||||
| import inet.ipaddr.IPAddress; | ||||
| import java.net.UnknownHostException; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.restrict.connection.RestrictConnection; | ||||
| import org.apache.guacamole.auth.restrict.user.RestrictUser; | ||||
| import org.apache.guacamole.auth.restrict.usergroup.RestrictUserGroup; | ||||
| import org.apache.guacamole.calendar.DailyRestriction; | ||||
| import org.apache.guacamole.calendar.TimeRestrictionParser; | ||||
| import org.apache.guacamole.host.HostRestrictionParser; | ||||
| import org.apache.guacamole.language.TranslatableGuacamoleSecurityException; | ||||
| import org.apache.guacamole.net.auth.AuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Directory; | ||||
| import org.apache.guacamole.net.auth.UserContext; | ||||
| import org.apache.guacamole.net.auth.UserGroup; | ||||
| import org.apache.guacamole.net.auth.permission.SystemPermission; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * Service for verifying additional user login restrictions against a given | ||||
|  * login attempt. | ||||
|  */ | ||||
| public class RestrictionVerificationService { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger LOGGER = LoggerFactory.getLogger(RestrictionVerificationService.class); | ||||
|  | ||||
|     /** | ||||
|      * Parse out the provided strings of allowed and denied times, verifying | ||||
|      * whether or not a login or connection should be allowed at the current | ||||
|      * day and time. A boolean true will be returned if the action should be | ||||
|      * allowed, otherwise false will be returned. | ||||
|      *  | ||||
|      * @param allowedTimeString | ||||
|      *     The string containing the times that should be parsed to determine if | ||||
|      *     the login or connection should be allowed at the current time, or | ||||
|      *     null or an empty string if there are no specific allowed times defined. | ||||
|      *  | ||||
|      * @param deniedTimeString | ||||
|      *     The string containing the times that should be parsed to determine if | ||||
|      *     the login or connection should be denied at the current time, or null | ||||
|      *     or an empty string if there are no specific times during which a | ||||
|      *     action should be denied. | ||||
|      *  | ||||
|      * @return | ||||
|      *     True if the login or connection should be allowed, otherwise false. | ||||
|      */ | ||||
|     private static boolean allowedByTimeRestrictions(String allowedTimeString, | ||||
|             String deniedTimeString) { | ||||
|          | ||||
|         // Check for denied entries, first, returning false if the login or | ||||
|         // connection should not be allowed. | ||||
|         if (deniedTimeString != null && !deniedTimeString.isEmpty()) { | ||||
|             List<DailyRestriction> deniedTimes =  | ||||
|                     TimeRestrictionParser.parseString(deniedTimeString); | ||||
|  | ||||
|             for (DailyRestriction restriction : deniedTimes) { | ||||
|                 if (restriction.appliesNow()) | ||||
|                     return false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // If no allowed entries are present, return true, allowing the login | ||||
|         // or connection to continue. | ||||
|         if (allowedTimeString == null || allowedTimeString.isEmpty()) | ||||
|             return true; | ||||
|          | ||||
|         List<DailyRestriction> allowedTimes =  | ||||
|                 TimeRestrictionParser.parseString(allowedTimeString); | ||||
|          | ||||
|         // Allowed entries are present, loop through them and check for a valid time. | ||||
|         for (DailyRestriction restriction : allowedTimes) { | ||||
|             // If this time allows the login or connection return true. | ||||
|             if (restriction.appliesNow()) | ||||
|                 return true; | ||||
|         } | ||||
|          | ||||
|         // We have allowed entries, but login hasn't matched, so deny it. | ||||
|         return false; | ||||
|          | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Given the strings of allowed and denied hosts, verify that the login or | ||||
|      * connection should be allowed from the given remote address. If the action | ||||
|      * should not be allowed, return false - otherwise, return true. | ||||
|      *  | ||||
|      * @param allowedHostsString | ||||
|      *     The string containing a semicolon-separated list of hosts from | ||||
|      *     which the login or connection should be allowed, or null or an empty | ||||
|      *     string if no specific set of allowed hosts is defined. | ||||
|      *  | ||||
|      * @param deniedHostsString | ||||
|      *     The string containing a semicolon-separated list of hosts from | ||||
|      *     which the login or connection should be denied, or null or an empty | ||||
|      *     string if no specific set of denied hosts is defined. | ||||
|      *  | ||||
|      * @param remoteAddress | ||||
|      *     The IP address from which the user is logging in or has logged in | ||||
|      *     and is attempting to connect from, if it is known. If it is unknown | ||||
|      *     and restrictions are defined, the login or connection will be denied. | ||||
|      *  | ||||
|      * @return | ||||
|      *     True if the login or connection should be allowed by the host-based | ||||
|      *     restrictions, otherwise false. | ||||
|      */ | ||||
|     private static boolean allowedByHostRestrictions(String allowedHostsString, | ||||
|             String deniedHostsString, String remoteAddress) { | ||||
|          | ||||
|         HostName remoteHostName = new HostName(remoteAddress); | ||||
|          | ||||
|         // If attributes do not exist or are empty then the action is allowed. | ||||
|         if ((allowedHostsString == null || allowedHostsString.isEmpty())  | ||||
|                 && (deniedHostsString == null || deniedHostsString.isEmpty())) | ||||
|             return true; | ||||
|          | ||||
|         // If the remote address cannot be determined, and restrictions are | ||||
|         // in effect, log an error and deny the action. | ||||
|         if (remoteAddress == null || remoteAddress.isEmpty()) { | ||||
|             LOGGER.warn("Host-based restrictions are present, but the remote " | ||||
|                     + "address is invalid or could not be resolved. " | ||||
|                     + "The action will not be allowed."); | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         // Split denied hosts attribute and process each entry, checking them | ||||
|         // against the current remote address, and returning false if a match is | ||||
|         // found. | ||||
|         List<HostName> deniedHosts = HostRestrictionParser.parseHostList(deniedHostsString); | ||||
|         for (HostName hostName : deniedHosts) { | ||||
|             try { | ||||
|                 if (hostName.isAddress() && hostName.toAddress().contains(remoteHostName.asAddress())) | ||||
|                     return false; | ||||
|  | ||||
|                 else | ||||
|                     for (IPAddress currAddr : hostName.toAllAddresses()) | ||||
|                         if (currAddr.matches(remoteHostName.asAddressString())) | ||||
|                             return false; | ||||
|             } | ||||
|             catch (UnknownHostException | HostNameException e) { | ||||
|                 LOGGER.warn("Unknown or invalid host in denied hosts list: \"{}\"", hostName); | ||||
|                 LOGGER.debug("Exception while trying to resolve host: \"{}\"", hostName, e); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // If denied hosts have been checked and allowed hosts are empty, we're | ||||
|         // good, and can allow the action. | ||||
|         if (allowedHostsString == null || allowedHostsString.isEmpty()) | ||||
|             return true; | ||||
|          | ||||
|         // Run through allowed hosts, if there are any, and return, allowing the | ||||
|         // action if there are any matches. | ||||
|         List<HostName> allowedHosts = HostRestrictionParser.parseHostList(allowedHostsString); | ||||
|         for (HostName hostName : allowedHosts) { | ||||
|             try { | ||||
|                 // If the entry is an IP or Subnet, check the remote address against it directly | ||||
|                 if (hostName.isAddress() && hostName.toAddress().contains(remoteHostName.asAddress())) | ||||
|                     return true; | ||||
|                  | ||||
|                 // Entry is a hostname, so resolve to IPs and check each one | ||||
|                 for (IPAddress currAddr : hostName.toAllAddresses()) | ||||
|                     if (currAddr.matches(remoteHostName.asAddressString())) | ||||
|                         return true; | ||||
|                  | ||||
|             } | ||||
|             // If an entry cannot be resolved we will log a warning. | ||||
|             catch (UnknownHostException | HostNameException e) { | ||||
|                 LOGGER.warn("Unknown host encountered in allowed host string: {}", hostName); | ||||
|                 LOGGER.debug("Exception received trying to resolve host: {}", hostName, e); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // If we've made it here, the allowed hosts do not contain the remote | ||||
|         // address, and the action should not be allowed; | ||||
|         return false; | ||||
|          | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Verifies the login restrictions supported by this extension for the user | ||||
|      * who is attempting to log in, throwing an exception if any of the | ||||
|      * restrictions result in the user not being allowed to log in. | ||||
|      *  | ||||
|      * @param context | ||||
|      *     The context of the user who is attempting to log in. | ||||
|      *  | ||||
|      * @param authenticatedUser | ||||
|      *     The AuthenticatedUser object associated with the user who is | ||||
|      *     attempting to log in. | ||||
|      *  | ||||
|      * @throws GuacamoleException  | ||||
|      *     If any of the restrictions should prevent the user from logging in. | ||||
|      */ | ||||
|     public static void verifyLoginRestrictions(UserContext context, | ||||
|             AuthenticatedUser authenticatedUser) throws GuacamoleException { | ||||
|          | ||||
|         // Get user's attributes | ||||
|         Map<String, String> userAttributes = context.self().getAttributes(); | ||||
|         String remoteAddress = authenticatedUser.getCredentials().getRemoteAddress(); | ||||
|          | ||||
|         if (context.self().getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.ADMINISTER)) { | ||||
|             LOGGER.warn("User \"{}\" has System Administration permissions; additional restrictions will be bypassed.",  | ||||
|                     authenticatedUser.getIdentifier()); | ||||
|             return; | ||||
|         } | ||||
|              | ||||
|         // Verify time-based restrictions specific to the user | ||||
|         String allowedTimeString = userAttributes.get(RestrictUser.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); | ||||
|         String deniedTimeString = userAttributes.get(RestrictUser.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); | ||||
|         if (!allowedByTimeRestrictions(allowedTimeString, deniedTimeString)) | ||||
|             throw new TranslatableInvalidTimeLoginException("User \""  | ||||
|                     + authenticatedUser.getIdentifier()  | ||||
|                     + "\" is not allowed to log in at this time.", | ||||
|                     "RESTRICT.ERROR_USER_LOGIN_NOT_ALLOWED_NOW"); | ||||
|          | ||||
|         // Verify host-based restrictions specific to the user | ||||
|         String allowedHostString = userAttributes.get(RestrictUser.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); | ||||
|         String deniedHostString = userAttributes.get(RestrictUser.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); | ||||
|         if (!allowedByHostRestrictions(allowedHostString, deniedHostString, remoteAddress)) | ||||
|             throw new TranslatableInvalidHostLoginException("User \""  | ||||
|                     + authenticatedUser.getIdentifier()  | ||||
|                     +"\" is not allowed to log in from \""  | ||||
|                     + remoteAddress + "\"", | ||||
|                     "RESTRICT.ERROR_USER_LOGIN_NOT_ALLOWED_FROM_HOST"); | ||||
|          | ||||
|         // Gather user's effective groups. | ||||
|         Set<String> userGroups = authenticatedUser.getEffectiveUserGroups(); | ||||
|         Directory<UserGroup> directoryGroups = context.getPrivileged().getUserGroupDirectory(); | ||||
|          | ||||
|         // Loop user's effective groups and verify restrictions | ||||
|         for (String userGroup : userGroups) { | ||||
|             UserGroup thisGroup = directoryGroups.get(userGroup); | ||||
|             if (thisGroup == null) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             // Get group's attributes | ||||
|             Map<String, String> grpAttributes = thisGroup.getAttributes(); | ||||
|              | ||||
|             // Pull time-based restrictions for this group and verify | ||||
|             String grpAllowedTimeString = grpAttributes.get(RestrictUserGroup.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); | ||||
|             String grpDeniedTimeString = grpAttributes.get(RestrictUserGroup.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); | ||||
|             if (!allowedByTimeRestrictions(grpAllowedTimeString, grpDeniedTimeString)) | ||||
|                 throw new TranslatableInvalidTimeLoginException("User \""  | ||||
|                         + authenticatedUser.getIdentifier()  | ||||
|                         +"\" is not allowed to log in at this time due to restrictions on group \""  | ||||
|                         + userGroup + "\".", | ||||
|                         "RESTRICT.ERROR_USER_LOGIN_NOT_ALLOWED_NOW"); | ||||
|              | ||||
|             // Pull host-based restrictions for this group and verify | ||||
|             String grpAllowedHostString = grpAttributes.get(RestrictUserGroup.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); | ||||
|             String grpDeniedHostString = grpAttributes.get(RestrictUserGroup.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); | ||||
|             if (!allowedByHostRestrictions(grpAllowedHostString, grpDeniedHostString, remoteAddress)) | ||||
|                 throw new TranslatableInvalidHostLoginException("User \""  | ||||
|                         + authenticatedUser.getIdentifier()  | ||||
|                         + "\" is not allowed to log in from this host due to restrictions on group \""  | ||||
|                         + userGroup + "\".", | ||||
|                         "RESTRICT.ERROR_USER_LOGIN_NOT_ALLOWED_FROM_HOST"); | ||||
|              | ||||
|         } | ||||
|          | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Verifies the connection restrictions supported by this extension for the | ||||
|      * connection the user is attempting to access, throwing an exception if | ||||
|      * any of the restrictions result in the connection being unavailable. | ||||
|      *  | ||||
|      * @param connectionAttributes | ||||
|      *     The attributes of the connection that may contain any additional | ||||
|      *     restrictions on use of the connection. | ||||
|      *  | ||||
|      * @param remoteAddress | ||||
|      *     The remote IP address of the user trying to access the connection. | ||||
|      *  | ||||
|      * @throws GuacamoleException  | ||||
|      *     If any of the restrictions should prevent the connection from being | ||||
|      *     used by the user at the current time. | ||||
|      */ | ||||
|     public static void verifyConnectionRestrictions( | ||||
|             Map<String, String> connectionAttributes, String remoteAddress) | ||||
|             throws GuacamoleException { | ||||
|          | ||||
|         // Verify time-based restrictions specific to this connection. | ||||
|         String allowedTimeString = connectionAttributes.get(RestrictConnection.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); | ||||
|         String deniedTimeString = connectionAttributes.get(RestrictConnection.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); | ||||
|         if (!allowedByTimeRestrictions(allowedTimeString, deniedTimeString)) | ||||
|             throw new TranslatableGuacamoleSecurityException( | ||||
|                     "Use of this connection is not allowed at this time.",  | ||||
|                     "RESTRICT.ERROR_CONNECTION_NOT_ALLOWED_NOW" | ||||
|             ); | ||||
|          | ||||
|         // Verify host-based restrictions specific to this connection. | ||||
|         String allowedHostString = connectionAttributes.get(RestrictConnection.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); | ||||
|         String deniedHostString = connectionAttributes.get(RestrictConnection.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); | ||||
|         if (!allowedByHostRestrictions(allowedHostString, deniedHostString, remoteAddress)) | ||||
|             throw new TranslatableGuacamoleSecurityException( | ||||
|                     "Use of this connection is not allowed from this remote host: \"" + remoteAddress + "\".",  | ||||
|                     "RESTRICT.ERROR_CONNECTION_NOT_ALLOWED_NOW" | ||||
|             ); | ||||
|          | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import org.apache.guacamole.language.TranslatableGuacamoleSecurityException; | ||||
| import org.apache.guacamole.language.TranslatableMessage; | ||||
|  | ||||
| /** | ||||
|  * An exception that represents an invalid login or connection due to | ||||
|  * restrictions based on the host from which the action should be allowed. | ||||
|  */ | ||||
| public class TranslatableInvalidHostConnectionException  | ||||
|         extends TranslatableGuacamoleSecurityException { | ||||
|  | ||||
|     /** | ||||
|      * The serial version ID of this class. | ||||
|      */ | ||||
|     private static final long serialVersionUID = 1L; | ||||
|      | ||||
|     /** | ||||
|      * Create a new host-based connection exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translatableMessage  | ||||
|      *     A translatable, human-readable description of the exception that | ||||
|      *     occurred. | ||||
|      */ | ||||
|     public TranslatableInvalidHostConnectionException(String message, | ||||
|             TranslatableMessage translatableMessage) { | ||||
|         super(message, translatableMessage); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new host-based connection exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translationKey | ||||
|      *     The arbitrary key which can be used to look up the message to be | ||||
|      *     displayed in the user's native language. | ||||
|      */ | ||||
|     public TranslatableInvalidHostConnectionException(String message, | ||||
|             String translationKey) { | ||||
|         super(message, new TranslatableMessage(translationKey)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,72 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import org.apache.guacamole.language.TranslatableGuacamoleClientException; | ||||
| import org.apache.guacamole.language.TranslatableMessage; | ||||
|  | ||||
| /** | ||||
|  * An exception that represents an invalid login or connection due to | ||||
|  * restrictions based on the host from which the action should be allowed. | ||||
|  */ | ||||
| public class TranslatableInvalidHostLoginException  | ||||
|         extends TranslatableGuacamoleClientException { | ||||
|  | ||||
|     /** | ||||
|      * The serial version ID of this class. | ||||
|      */ | ||||
|     private static final long serialVersionUID = 1L; | ||||
|      | ||||
|     /** | ||||
|      * Create a new host-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translatableMessage  | ||||
|      *     A translatable, human-readable description of the exception that | ||||
|      *     occurred. | ||||
|      */ | ||||
|     public TranslatableInvalidHostLoginException(String message, | ||||
|             TranslatableMessage translatableMessage) { | ||||
|         super(message, translatableMessage); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new host-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translationKey | ||||
|      *     The arbitrary key which can be used to look up the message to be | ||||
|      *     displayed in the user's native language. | ||||
|      */ | ||||
|     public TranslatableInvalidHostLoginException(String message, String translationKey) { | ||||
|         super(message, new TranslatableMessage(translationKey)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import org.apache.guacamole.language.TranslatableGuacamoleSecurityException; | ||||
| import org.apache.guacamole.language.TranslatableMessage; | ||||
|  | ||||
| /** | ||||
|  * An exception that represents an invalid login due to restrictions based | ||||
|  * on the time of day and day of week the user is allowed to log in. | ||||
|  */ | ||||
| public class TranslatableInvalidTimeConnectionException  | ||||
|         extends TranslatableGuacamoleSecurityException { | ||||
|  | ||||
|     /** | ||||
|      * The serial version ID of this class. | ||||
|      */ | ||||
|     private static final long serialVersionUID = 1L; | ||||
|      | ||||
|     /** | ||||
|      * Create a new time-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translatableMessage  | ||||
|      *     A translatable, human-readable description of the exception that | ||||
|      *     occurred. | ||||
|      */ | ||||
|     public TranslatableInvalidTimeConnectionException(String message, | ||||
|             TranslatableMessage translatableMessage) { | ||||
|         super(message, translatableMessage); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new time-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translationKey  | ||||
|      *     The arbitrary key which can be used to look up the message to be | ||||
|      *     displayed in the user's native language. | ||||
|      */ | ||||
|     public TranslatableInvalidTimeConnectionException(String message, | ||||
|             String translationKey) { | ||||
|         super(message, new TranslatableMessage(translationKey)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict; | ||||
|  | ||||
| import org.apache.guacamole.language.TranslatableGuacamoleClientException; | ||||
| import org.apache.guacamole.language.TranslatableMessage; | ||||
|  | ||||
| /** | ||||
|  * An exception that represents an invalid login due to restrictions based | ||||
|  * on the time of day and day of week the user is allowed to log in. | ||||
|  */ | ||||
| public class TranslatableInvalidTimeLoginException  | ||||
|         extends TranslatableGuacamoleClientException { | ||||
|  | ||||
|     /** | ||||
|      * The serial version ID of this class. | ||||
|      */ | ||||
|     private static final long serialVersionUID = 1L; | ||||
|      | ||||
|     /** | ||||
|      * Create a new time-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translatableMessage  | ||||
|      *     A translatable, human-readable description of the exception that | ||||
|      *     occurred. | ||||
|      */ | ||||
|     public TranslatableInvalidTimeLoginException(String message, | ||||
|             TranslatableMessage translatableMessage) { | ||||
|         super(message, translatableMessage); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new time-based login exception with the given message and | ||||
|      * translation string that can be processed by Guacamole's translation | ||||
|      * service. | ||||
|      *  | ||||
|      * @param message | ||||
|      *     The non-translatable, human-readable message containing details | ||||
|      *     of the exception. | ||||
|      *  | ||||
|      * @param translationKey  | ||||
|      *     The arbitrary key which can be used to look up the message to be | ||||
|      *     displayed in the user's native language. | ||||
|      */ | ||||
|     public TranslatableInvalidTimeLoginException(String message, | ||||
|             String translationKey) { | ||||
|         super(message, new TranslatableMessage(translationKey)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,185 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.connection; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.restrict.RestrictionVerificationService; | ||||
| import org.apache.guacamole.auth.restrict.form.HostRestrictionField; | ||||
| import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; | ||||
| import org.apache.guacamole.form.Form; | ||||
| import org.apache.guacamole.net.GuacamoleTunnel; | ||||
| import org.apache.guacamole.net.auth.Connection; | ||||
| import org.apache.guacamole.net.auth.DelegatingConnection; | ||||
| import org.apache.guacamole.protocol.GuacamoleClientInformation; | ||||
|  | ||||
| /** | ||||
|  * A Connection implementation that wraps another connection, providing additional | ||||
|  * ability to control access to the connection. | ||||
|  */ | ||||
| public class RestrictConnection extends DelegatingConnection { | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that this connection can be accessed. The presence of values within this | ||||
|      * attribute will automatically restrict use of the connections at any | ||||
|      * times that are not specified. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-time-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that this connection cannot be accessed. Denied times will always take | ||||
|      * precedence over allowed times. The presence of this attribute without | ||||
|      * guac-restrict-time-allowed will deny access only during the times listed | ||||
|      * in this attribute, allowing access at all other times. The presence of | ||||
|      * this attribute along with the guac-restrict-time-allowed attribute will | ||||
|      * deny access at any times that overlap with the allowed times. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_DENIED_ATTRIBUTE_NAME = "guac-restrict-time-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of hosts from which a user | ||||
|      * may access this connection. The presence of this attribute will restrict | ||||
|      * access to only users accessing Guacamole from the list of hosts contained | ||||
|      * in the attribute, subject to further restriction by the | ||||
|      * guac-restrict-hosts-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-hosts-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of hosts from which | ||||
|      * a user may not access this connection. The presence of this attribute, | ||||
|      * absent the guac-restrict-hosts-allowed attribute, will allow access from | ||||
|      * all hosts except the ones listed in this attribute. The presence of this | ||||
|      * attribute coupled with the guac-restrict-hosts-allowed attribute will | ||||
|      * block access from any IPs in this list, overriding any that may be | ||||
|      * allowed. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME = "guac-restrict-hosts-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The list of all connection attributes provided by this Connection implementation. | ||||
|      */ | ||||
|     public static final List<String> RESTRICT_CONNECTION_ATTRIBUTES = Arrays.asList( | ||||
|             RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The form containing the list of fields for the attributes provided | ||||
|      * by this module. | ||||
|      */ | ||||
|     public static final Form RESTRICT_CONNECTION_FORM = new Form("restrict-login-form", | ||||
|             Arrays.asList( | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME) | ||||
|             ) | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The remote address from which the user attempting to access this | ||||
|      * connection logged in. | ||||
|      */ | ||||
|     private final String remoteAddress; | ||||
|      | ||||
|     /** | ||||
|      * Wraps the given Connection object, providing capability of further | ||||
|      * restricting connection access beyond the default access control provided | ||||
|      * by other modules. | ||||
|      * | ||||
|      * @param connection | ||||
|      *     The Connection object to wrap. | ||||
|      *  | ||||
|      * @param remoteAddress | ||||
|      *     The remote address from which the user attempting to access this | ||||
|      *     connection logged in. | ||||
|      */ | ||||
|     public RestrictConnection(Connection connection, String remoteAddress) { | ||||
|         super(connection); | ||||
|         this.remoteAddress = remoteAddress; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Returns the original Connection object wrapped by this RestrictConnection. | ||||
|      * | ||||
|      * @return | ||||
|      *     The wrapped Connection object. | ||||
|      */ | ||||
|     public Connection getUndecorated() { | ||||
|         return getDelegateConnection(); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Map<String, String> getAttributes() { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         Map<String, String> attributes = new HashMap<>(super.getAttributes()); | ||||
|          | ||||
|         // Loop through extension-specific attributes and add them where no | ||||
|         // values exist, so that they show up in the web UI. | ||||
|         for (String attribute : RESTRICT_CONNECTION_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value == null || value.isEmpty()) | ||||
|                 attributes.put(attribute,  null); | ||||
|         } | ||||
|  | ||||
|         return attributes; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setAttributes(Map<String, String> attributes) { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         attributes = new HashMap<>(attributes); | ||||
|  | ||||
|         // Loop through extension-specific attributes, only sending ones | ||||
|         // that are non-null and non-empty to the underlying storage mechanism. | ||||
|         for (String attribute : RESTRICT_CONNECTION_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value != null && value.isEmpty()) | ||||
|                 attributes.put(attribute, null); | ||||
|         } | ||||
|  | ||||
|         super.setAttributes(attributes); | ||||
|  | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public GuacamoleTunnel connect(GuacamoleClientInformation info, | ||||
|             Map<String, String> tokens) throws GuacamoleException { | ||||
|          | ||||
|         // Verify the restrictions for this connection. | ||||
|         RestrictionVerificationService.verifyConnectionRestrictions(getAttributes(), remoteAddress); | ||||
|          | ||||
|         // Connect | ||||
|         return super.connect(info, tokens); | ||||
|          | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,187 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.connectiongroup; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.restrict.RestrictionVerificationService; | ||||
| import org.apache.guacamole.auth.restrict.form.HostRestrictionField; | ||||
| import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; | ||||
| import org.apache.guacamole.form.Form; | ||||
| import org.apache.guacamole.net.GuacamoleTunnel; | ||||
| import org.apache.guacamole.net.auth.ConnectionGroup; | ||||
| import org.apache.guacamole.net.auth.DelegatingConnectionGroup; | ||||
| import org.apache.guacamole.protocol.GuacamoleClientInformation; | ||||
|  | ||||
| /** | ||||
|  * A ConnectionGroup implementation that wraps an existing ConnectionGroup, | ||||
|  * providing additional ability to control access to the ConnectionGroup. | ||||
|  */ | ||||
| public class RestrictConnectionGroup extends DelegatingConnectionGroup { | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that this connection group can be accessed. The presence of values within | ||||
|      * this attribute will automatically restrict use of the connection group | ||||
|      * at any times that are not specified. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-time-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that this connection group cannot be accessed. Denied times will always | ||||
|      * take precedence over allowed times. The presence of this attribute without | ||||
|      * guac-restrict-time-allowed will deny access only during the times listed | ||||
|      * in this attribute, allowing access at all other times. The presence of | ||||
|      * this attribute along with the guac-restrict-time-allowed attribute will | ||||
|      * deny access at any times that overlap with the allowed times. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_DENIED_ATTRIBUTE_NAME = "guac-restrict-time-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of hosts from which a user | ||||
|      * may access this connection group. The presence of this attribute will | ||||
|      * restrict access to only users accessing Guacamole from the list of hosts | ||||
|      * contained in the attribute, subject to further restriction by the | ||||
|      * guac-restrict-hosts-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-hosts-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of hosts from which | ||||
|      * a user may not access this connection group. The presence of this | ||||
|      * attribute, absent the guac-restrict-hosts-allowed attribute, will allow | ||||
|      * access from all hosts except the ones listed in this attribute. The | ||||
|      * presence of this attribute coupled with the guac-restrict-hosts-allowed | ||||
|      * attribute will block access from any hosts in this list, overriding any | ||||
|      * that may be allowed. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME = "guac-restrict-hosts-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The list of all connection group attributes provided by this | ||||
|      * ConnectionGroup implementation. | ||||
|      */ | ||||
|     public static final List<String> RESTRICT_CONNECTIONGROUP_ATTRIBUTES = Arrays.asList( | ||||
|             RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The form containing the list of fields for the attributes provided | ||||
|      * by this ConnectionGroup implementation. | ||||
|      */ | ||||
|     public static final Form RESTRICT_CONNECTIONGROUP_FORM = new Form("restrict-login-form", | ||||
|             Arrays.asList( | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME) | ||||
|             ) | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The remote address from which the user accessing this connection group | ||||
|      * logged in. | ||||
|      */ | ||||
|     private final String remoteAddress; | ||||
|      | ||||
|     /** | ||||
|      * Wraps the given ConnectionGroup object, providing capability of further | ||||
|      * restricting connection group access beyond the default access control | ||||
|      * provided by other modules. | ||||
|      * | ||||
|      * @param connectionGroup | ||||
|      *     The ConnectionGroup object to wrap. | ||||
|      *  | ||||
|      * @param remoteAddress | ||||
|      *     The remote address from which the user accessing this connection | ||||
|      *     logged in. | ||||
|      */ | ||||
|     public RestrictConnectionGroup(ConnectionGroup connectionGroup, String remoteAddress) { | ||||
|         super(connectionGroup); | ||||
|         this.remoteAddress = remoteAddress; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Returns the original ConnectionGroup object wrapped by this | ||||
|      * RestrictConnectionGroup. | ||||
|      * | ||||
|      * @return | ||||
|      *     The wrapped ConnectionGroup object. | ||||
|      */ | ||||
|     public ConnectionGroup getUndecorated() { | ||||
|         return getDelegateConnectionGroup(); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Map<String, String> getAttributes() { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         Map<String, String> attributes = new HashMap<>(super.getAttributes()); | ||||
|          | ||||
|         // Loop through extension-specific attributes and add them where no | ||||
|         // values exist, so that they show up in the web UI. | ||||
|         for (String attribute : RESTRICT_CONNECTIONGROUP_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value == null || value.isEmpty()) | ||||
|                 attributes.put(attribute, null); | ||||
|         } | ||||
|  | ||||
|         return attributes; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setAttributes(Map<String, String> attributes) { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         attributes = new HashMap<>(attributes); | ||||
|  | ||||
|         // Loop through extension-specific attributes, only sending ones | ||||
|         // that are non-null and non-empty to the underlying storage mechanism. | ||||
|         for (String attribute : RESTRICT_CONNECTIONGROUP_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value != null && value.isEmpty()) | ||||
|                 attributes.put(attribute, null); | ||||
|         } | ||||
|  | ||||
|         super.setAttributes(attributes); | ||||
|  | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public GuacamoleTunnel connect(GuacamoleClientInformation info, | ||||
|             Map<String, String> tokens) throws GuacamoleException { | ||||
|          | ||||
|         // Verify restrictions for this connection group. | ||||
|         RestrictionVerificationService.verifyConnectionRestrictions(getAttributes(), remoteAddress); | ||||
|          | ||||
|         // Connect | ||||
|         return super.connect(info, tokens); | ||||
|          | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,47 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.form; | ||||
|  | ||||
| import org.apache.guacamole.form.Field; | ||||
|  | ||||
| /** | ||||
|  * A field that parses out a string of semi-colon separated hosts into | ||||
|  * individual entries that can be managed more easily in a web interface. | ||||
|  */ | ||||
| public class HostRestrictionField extends Field { | ||||
|      | ||||
|     /** | ||||
|      * The field type. | ||||
|      */ | ||||
|     public static final String FIELD_TYPE = "GUAC_HOST_RESTRICTION"; | ||||
|      | ||||
|     /** | ||||
|      * Create a new field that tracks host restrictions. | ||||
|      *  | ||||
|      * @param name | ||||
|      *     The name of the parameter that will be used to pass this field | ||||
|      *     between the REST API and the web front-end. | ||||
|      *  | ||||
|      */ | ||||
|     public HostRestrictionField(String name) { | ||||
|         super(name, FIELD_TYPE); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,49 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.form; | ||||
|  | ||||
| import org.apache.guacamole.form.Field; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * A field that parses a string containing time restrictions into its individual | ||||
|  * components for user-friendly display on the web interface. | ||||
|  */ | ||||
| public class TimeRestrictionField extends Field { | ||||
|      | ||||
|     /** | ||||
|      * The field type. | ||||
|      */ | ||||
|     public static final String FIELD_TYPE = "GUAC_TIME_RESTRICTION"; | ||||
|      | ||||
|     /** | ||||
|      * Create a new field that tracks time restrictions. | ||||
|      *  | ||||
|      * @param name | ||||
|      *     The name of the parameter that will be used to pass this field | ||||
|      *     between the REST API and the web front-end. | ||||
|      *  | ||||
|      */ | ||||
|     public TimeRestrictionField(String name) { | ||||
|         super(name, FIELD_TYPE); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,158 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.user; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.guacamole.auth.restrict.form.HostRestrictionField; | ||||
| import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; | ||||
| import org.apache.guacamole.form.Form; | ||||
| import org.apache.guacamole.net.auth.DelegatingUser; | ||||
| import org.apache.guacamole.net.auth.User; | ||||
|  | ||||
| /** | ||||
|  * User implementation which wraps a User from another extension and enforces | ||||
|  * additional restrictions. | ||||
|  */ | ||||
| public class RestrictUser extends DelegatingUser { | ||||
|  | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that a user is allowed to log in. The presence of this attribute will | ||||
|      * restrict the user to logins only during the times that are contained | ||||
|      * within the attribute, subject to further restriction by the | ||||
|      * guac-restrict-time-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-time-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that a user is not allowed to log in. Denied times will always take | ||||
|      * precedence over allowed times. The presence of this attribute without | ||||
|      * guac-restrict-time-allowed will deny logins only during the times listed | ||||
|      * in this attribute, allowing logins at all other times. The presence of | ||||
|      * this attribute along with the guac-restrict-time-allowed attribute will | ||||
|      * deny logins at any times that overlap with the allowed times. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_DENIED_ATTRIBUTE_NAME = "guac-restrict-time-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of IP addresses from which | ||||
|      * a user is allowed to log in. The presence of this attribute will restrict | ||||
|      * users to only the list of IP addresses contained in the attribute, subject | ||||
|      * to further restriction by the guac-restrict-hosts-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-hosts-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of IP addresses from which | ||||
|      * a user is not allowed to log in. The presence of this attribute, absent | ||||
|      * the guac-restrict-hosts-allowed attribute, will allow logins from all | ||||
|      * hosts except the ones listed in this attribute. The presence of this | ||||
|      * attribute coupled with the guac-restrict-hosts-allowed attribute will | ||||
|      * block access from any IPs in this list, overriding any that may be | ||||
|      * allowed. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME = "guac-restrict-hosts-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The list of all user attributes provided by this User implementation. | ||||
|      */ | ||||
|     public static final List<String> RESTRICT_USER_ATTRIBUTES = Arrays.asList( | ||||
|             RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The form containing the list of fields for the attributes provided | ||||
|      * by this module. | ||||
|      */ | ||||
|     public static final Form RESTRICT_LOGIN_FORM = new Form("restrict-login-form", | ||||
|             Arrays.asList( | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME) | ||||
|             ) | ||||
|     ); | ||||
|      | ||||
|      | ||||
|     /** | ||||
|      * Wraps the given User object, providing capability of further restricting | ||||
|      * logins beyond the default restrictions provided by default modules. | ||||
|      * | ||||
|      * @param user | ||||
|      *     The User object to wrap. | ||||
|      */ | ||||
|     public RestrictUser(User user) { | ||||
|         super(user); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the User object wrapped by this RestrictUser. | ||||
|      * | ||||
|      * @return | ||||
|      *     The wrapped User object. | ||||
|      */ | ||||
|     public User getUndecorated() { | ||||
|         return getDelegateUser(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, String> getAttributes() { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         Map<String, String> attributes = new HashMap<>(super.getAttributes()); | ||||
|          | ||||
|         // Loop through extension-specific attributes, adding ones that are | ||||
|         // empty so that they are displayed in the web UI. | ||||
|         for (String attribute : RESTRICT_USER_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value == null || value.isEmpty()) | ||||
|                 attributes.put(attribute,  null); | ||||
|         } | ||||
|  | ||||
|         return attributes; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setAttributes(Map<String, String> attributes) { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         attributes = new HashMap<>(attributes); | ||||
|  | ||||
|         // Loop through extension-specific attributes, only sending ones | ||||
|         // that are non-null and non-empty to the underlying storage mechanism. | ||||
|         for (String attribute : RESTRICT_USER_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value != null && value.isEmpty()) | ||||
|                 attributes.put(attribute, null); | ||||
|         } | ||||
|  | ||||
|         super.setAttributes(attributes); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,165 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.user; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.auth.restrict.connection.RestrictConnection; | ||||
| import org.apache.guacamole.auth.restrict.connectiongroup.RestrictConnectionGroup; | ||||
| import org.apache.guacamole.auth.restrict.usergroup.RestrictUserGroup; | ||||
| import org.apache.guacamole.form.Form; | ||||
| import org.apache.guacamole.net.auth.Connection; | ||||
| import org.apache.guacamole.net.auth.ConnectionGroup; | ||||
| import org.apache.guacamole.net.auth.DecoratingDirectory; | ||||
| import org.apache.guacamole.net.auth.DelegatingUserContext; | ||||
| import org.apache.guacamole.net.auth.Directory; | ||||
| import org.apache.guacamole.net.auth.User; | ||||
| import org.apache.guacamole.net.auth.UserContext; | ||||
| import org.apache.guacamole.net.auth.UserGroup; | ||||
|  | ||||
| /** | ||||
|  * A UserContext implementation for additional login and connection restrictions | ||||
|  * which wraps the UserContext of some other extension. | ||||
|  */ | ||||
| public class RestrictUserContext extends DelegatingUserContext { | ||||
|  | ||||
|     /** | ||||
|      * The remote address from which this user logged in. | ||||
|      */ | ||||
|     private final String remoteAddress; | ||||
|      | ||||
|     /** | ||||
|      * Creates a new RestrictUserContext which wraps the given UserContext, | ||||
|      * providing additional control for user logins and connections. | ||||
|      * | ||||
|      * @param userContext | ||||
|      *     The UserContext to wrap. | ||||
|      *  | ||||
|      * @param remoteAddress | ||||
|      *     The address the user is logging in from, if known. | ||||
|      */ | ||||
|     public RestrictUserContext(UserContext userContext, String remoteAddress) { | ||||
|         super(userContext); | ||||
|         this.remoteAddress = remoteAddress; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Directory<Connection> getConnectionDirectory() throws GuacamoleException { | ||||
|         return new DecoratingDirectory<Connection>(super.getConnectionDirectory()) { | ||||
|  | ||||
|             @Override | ||||
|             protected Connection decorate(Connection object) { | ||||
|                 return new RestrictConnection(object, remoteAddress); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected Connection undecorate(Connection object) { | ||||
|                 assert(object instanceof RestrictConnection); | ||||
|                 return ((RestrictConnection) object).getUndecorated(); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Collection<Form> getConnectionAttributes() { | ||||
|         Collection<Form> connectionAttrs = new HashSet<>(super.getConnectionAttributes()); | ||||
|         connectionAttrs.add(RestrictConnection.RESTRICT_CONNECTION_FORM); | ||||
|         return Collections.unmodifiableCollection(connectionAttrs); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Directory<ConnectionGroup> getConnectionGroupDirectory() throws GuacamoleException { | ||||
|         return new DecoratingDirectory<ConnectionGroup>(super.getConnectionGroupDirectory()) { | ||||
|  | ||||
|             @Override | ||||
|             protected ConnectionGroup decorate(ConnectionGroup object) { | ||||
|                 return new RestrictConnectionGroup(object, remoteAddress); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected ConnectionGroup undecorate(ConnectionGroup object) { | ||||
|                 assert(object instanceof RestrictConnectionGroup); | ||||
|                 return ((RestrictConnectionGroup) object).getUndecorated(); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Collection<Form> getConnectionGroupAttributes() { | ||||
|         Collection<Form> connectionGroupAttrs = new HashSet<>(super.getConnectionGroupAttributes()); | ||||
|         connectionGroupAttrs.add(RestrictConnectionGroup.RESTRICT_CONNECTIONGROUP_FORM); | ||||
|         return Collections.unmodifiableCollection(connectionGroupAttrs); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Directory<User> getUserDirectory() throws GuacamoleException { | ||||
|         return new DecoratingDirectory<User>(super.getUserDirectory()) { | ||||
|  | ||||
|             @Override | ||||
|             protected User decorate(User object) { | ||||
|                 return new RestrictUser(object); | ||||
|             } | ||||
|  | ||||
|             @Override | ||||
|             protected User undecorate(User object) { | ||||
|                 assert(object instanceof RestrictUser); | ||||
|                 return ((RestrictUser) object).getUndecorated(); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Collection<Form> getUserAttributes() { | ||||
|         Collection<Form> userAttrs = new HashSet<>(super.getUserAttributes()); | ||||
|         userAttrs.add(RestrictUser.RESTRICT_LOGIN_FORM); | ||||
|         return Collections.unmodifiableCollection(userAttrs); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Directory<UserGroup> getUserGroupDirectory() throws GuacamoleException { | ||||
|         return new DecoratingDirectory<UserGroup>(super.getUserGroupDirectory()) { | ||||
|              | ||||
|             @Override | ||||
|             protected UserGroup decorate(UserGroup object) { | ||||
|                 return new RestrictUserGroup(object); | ||||
|             } | ||||
|              | ||||
|             @Override | ||||
|             protected UserGroup undecorate(UserGroup object) { | ||||
|                 assert(object instanceof RestrictUserGroup); | ||||
|                 return ((RestrictUserGroup) object).getUndecorated(); | ||||
|             } | ||||
|              | ||||
|         }; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public Collection<Form> getUserGroupAttributes() { | ||||
|         Collection<Form> userGroupAttrs = new HashSet<>(super.getUserGroupAttributes()); | ||||
|         userGroupAttrs.add(RestrictUserGroup.RESTRICT_LOGIN_FORM); | ||||
|         return Collections.unmodifiableCollection(userGroupAttrs); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,160 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.auth.restrict.usergroup; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import org.apache.guacamole.auth.restrict.form.HostRestrictionField; | ||||
| import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; | ||||
| import org.apache.guacamole.form.Form; | ||||
| import org.apache.guacamole.net.auth.DelegatingUserGroup; | ||||
| import org.apache.guacamole.net.auth.UserGroup; | ||||
|  | ||||
| /** | ||||
|  * UserGroup implementation which wraps a UserGroup from another extension and | ||||
|  * enforces additional restrictions for members of that group. | ||||
|  */ | ||||
| public class RestrictUserGroup extends DelegatingUserGroup { | ||||
|  | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that members of a group are allowed to log in. The presence of this | ||||
|      * attribute will restrict any users who are members of the group to logins | ||||
|      * only during the times that are contained within the attribute, | ||||
|      * subject to further restriction by the guac-restrict-time-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-time-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of weekdays and times (UTC) | ||||
|      * that members of a group are not allowed to log in. Denied times will | ||||
|      * always take precedence over allowed times. The presence of this attribute | ||||
|      * without guac-restrict-time-allowed will deny logins only during the times | ||||
|      * listed in this attribute, allowing logins at all other times. The | ||||
|      * presence of this attribute along with the guac-restrict-time-allowed | ||||
|      * attribute will deny logins at any times that overlap with the allowed | ||||
|      * times. | ||||
|      */ | ||||
|     public static final String RESTRICT_TIME_DENIED_ATTRIBUTE_NAME = "guac-restrict-time-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of IP addresses from which | ||||
|      * members of a group are allowed to log in. The presence of this attribute | ||||
|      * will restrict users to only the list of IP addresses contained in the | ||||
|      * attribute, subject to further restriction by the | ||||
|      * guac-restrict-hosts-denied attribute. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME = "guac-restrict-hosts-allowed"; | ||||
|      | ||||
|     /** | ||||
|      * The name of the attribute that contains a list of IP addresses from which | ||||
|      * members of a group are not allowed to log in. The presence of this | ||||
|      * attribute, absent the guac-restrict-hosts-allowed attribute, will allow | ||||
|      * logins from all hosts except the ones listed in this attribute. The | ||||
|      * presence of this attribute coupled with the guac-restrict-hosts-allowed | ||||
|      * attribute will block access from any IPs in this list, overriding any | ||||
|      * that may be allowed. | ||||
|      */ | ||||
|     public static final String RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME = "guac-restrict-hosts-denied"; | ||||
|      | ||||
|     /** | ||||
|      * The list of all user attributes provided by this UserGroup implementation. | ||||
|      */ | ||||
|     public static final List<String> RESTRICT_USERGROUP_ATTRIBUTES = Arrays.asList( | ||||
|             RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, | ||||
|             RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * The form containing the list of fields for the attributes provided | ||||
|      * by this module. | ||||
|      */ | ||||
|     public static final Form RESTRICT_LOGIN_FORM = new Form("restrict-login-form", | ||||
|             Arrays.asList( | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), | ||||
|                     new HostRestrictionField(RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME) | ||||
|             ) | ||||
|     ); | ||||
|      | ||||
|      | ||||
|     /** | ||||
|      * Wraps the given UserGroup object, providing capability of further restricting | ||||
|      * logins beyond the default restrictions provided by default modules. | ||||
|      * | ||||
|      * @param userGroup | ||||
|      *     The UserGroup object to wrap. | ||||
|      */ | ||||
|     public RestrictUserGroup(UserGroup userGroup) { | ||||
|         super(userGroup); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the UserGroup object wrapped by this RestrictUserGroup. | ||||
|      * | ||||
|      * @return | ||||
|      *     The wrapped UserGroup object. | ||||
|      */ | ||||
|     public UserGroup getUndecorated() { | ||||
|         return getDelegateUserGroupGroup(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, String> getAttributes() { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         Map<String, String> attributes = new HashMap<>(super.getAttributes()); | ||||
|          | ||||
|         // Loop through extension-specific attributes, adding ones that are | ||||
|         // empty so that they are displayed in the web UI. | ||||
|         for (String attribute : RESTRICT_USERGROUP_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value == null || value.isEmpty()) | ||||
|                 attributes.put(attribute,  null); | ||||
|         } | ||||
|  | ||||
|         return attributes; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void setAttributes(Map<String, String> attributes) { | ||||
|  | ||||
|         // Create independent, mutable copy of attributes | ||||
|         attributes = new HashMap<>(attributes); | ||||
|  | ||||
|         // Loop through extension-specific attributes, only sending ones | ||||
|         // that are non-null and non-empty to the underlying storage mechanism. | ||||
|         for (String attribute : RESTRICT_USERGROUP_ATTRIBUTES) { | ||||
|             String value = attributes.get(attribute); | ||||
|             if (value != null && value.isEmpty()) | ||||
|                 attributes.put(attribute, null); | ||||
|         } | ||||
|  | ||||
|         super.setAttributes(attributes); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,132 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.calendar; | ||||
|  | ||||
| import java.time.DayOfWeek; | ||||
| import java.time.LocalDate; | ||||
| import java.time.LocalTime; | ||||
| import java.time.ZoneId; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * A class that stores a daily time restriction that can be used to determine | ||||
|  * whether or not a user can log in on a certain day of the week and during | ||||
|  * a certain time window. | ||||
|  */ | ||||
| public class DailyRestriction { | ||||
|      | ||||
|     /** | ||||
|      * The days of the week that this restriction applies to. | ||||
|      */ | ||||
|     private final List<DayOfWeek> weekDays; | ||||
|      | ||||
|     /** | ||||
|      * The time that the restriction starts. | ||||
|      */ | ||||
|     private final LocalTime startTime; | ||||
|      | ||||
|     /** | ||||
|      * The time that the restriction ends. | ||||
|      */ | ||||
|     private final LocalTime endTime; | ||||
|      | ||||
|     /** | ||||
|      * Create a new daily restriction with the specified day of the week, start | ||||
|      * time, and end time. | ||||
|      *  | ||||
|      * @param weekDay | ||||
|      *     The day of the week that this restriction should apply to. | ||||
|      *  | ||||
|      * @param startTime | ||||
|      *     The start time of the restriction. | ||||
|      *  | ||||
|      * @param endTime  | ||||
|      *     The end time of the restriction. | ||||
|      */ | ||||
|     public DailyRestriction(DayOfWeek weekDay, | ||||
|             LocalTime startTime, LocalTime endTime) { | ||||
|         this.weekDays = Collections.singletonList(weekDay); | ||||
|         this.startTime = startTime; | ||||
|         this.endTime = endTime; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new daily restriction with the specified days of the week, start | ||||
|      * time, and end time. | ||||
|      *  | ||||
|      * @param weekDays | ||||
|      *     The days of the week that this restriction should apply to. | ||||
|      *  | ||||
|      * @param startTime | ||||
|      *     The start time of the restriction. | ||||
|      *  | ||||
|      * @param endTime  | ||||
|      *     The end time of the restriction. | ||||
|      */ | ||||
|     public DailyRestriction(List<DayOfWeek> weekDays, | ||||
|             LocalTime startTime, LocalTime endTime) { | ||||
|         this.weekDays = weekDays; | ||||
|         this.startTime = startTime; | ||||
|         this.endTime = endTime; | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new daily restriction for an entire day, settings the start | ||||
|      * time at midnight and the end time at the end of the day (235959). | ||||
|      *  | ||||
|      * @param weekDay | ||||
|      *     The day of the week that this restriction should apply to. | ||||
|      */ | ||||
|     public DailyRestriction(DayOfWeek weekDay) { | ||||
|         this.weekDays = Collections.singletonList(weekDay); | ||||
|         this.startTime = LocalTime.of(0, 0, 0); | ||||
|         this.endTime = LocalTime.of(23, 59, 59); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Create a new daily restriction for entire days, settings the start | ||||
|      * time at midnight and the end time at the end of the day (235959). | ||||
|      *  | ||||
|      * @param weekDays | ||||
|      *     The days of the week that this restriction should apply to. | ||||
|      */ | ||||
|     public DailyRestriction(List<DayOfWeek> weekDays) { | ||||
|         this.weekDays = weekDays; | ||||
|         this.startTime = LocalTime.of(0, 0, 0); | ||||
|         this.endTime = LocalTime.of(23, 59, 59); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
|      * Returns true if this restriction applies now, otherwise false. | ||||
|      *  | ||||
|      * @return  | ||||
|      *     true if the current time of day falls within this restriction, | ||||
|      *     otherwise false. | ||||
|      */ | ||||
|     public boolean appliesNow() { | ||||
|         DayOfWeek currentDay = LocalDate.now().getDayOfWeek(); | ||||
|         LocalTime currentTime = LocalTime.now(ZoneId.of("UTC")); | ||||
|          | ||||
|         // Check that we are in the specified time restriction | ||||
|         return (weekDays.contains(currentDay) && currentTime.isAfter(startTime) && currentTime.isBefore(endTime)); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,180 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.calendar; | ||||
|  | ||||
| import java.time.DayOfWeek; | ||||
| import java.time.LocalTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * A class for parsing time-based restrictions stored in a String into other | ||||
|  * formats that can be used by Guacamole. | ||||
|  */ | ||||
| public class TimeRestrictionParser { | ||||
|      | ||||
|     /** | ||||
|      * The compiled regular expression that matches one or more instances of | ||||
|      * a restriction string, which specifies at least one day and time range | ||||
|      * that the restriction applies to. | ||||
|      *  | ||||
|      * <p>Examples of valid restrictions are as follows: | ||||
|      * <ul> | ||||
|      * <li>1:0700-1700 - Monday from 07:00 to 17:00 | ||||
|      * <li>7:0000-2359 - Sunday, all day (00:00 to 23:59) | ||||
|      * <li>wd:0900-1700 - Monday through Friday, 09:00 to 17:00 | ||||
|      * <li>we:0900-1700 - Saturday and Sunday, 09:00 to 17:00 | ||||
|      * <li>6:0900-1600;7:1200-1300 - Saturday, 09:00 to 16:00, and Sunday,  | ||||
|      * 12:00 - 13:00 | ||||
|      * </ul> | ||||
|      */ | ||||
|     private static final Pattern RESTRICTION_REGEX =  | ||||
|             Pattern.compile("(?:^|;)+([1-7*]|(?:[w][ed]))(?::((?:[01][0-9]|2[0-3])[0-5][0-9])\\-((?:[01][0-9]|2[0-3])[0-5][0-9]))+"); | ||||
|      | ||||
|     /** | ||||
|      * The RegEx group that contains the start day-of-week of the restriction. | ||||
|      */ | ||||
|     private static final int RESTRICTION_DAY_GROUP = 1; | ||||
|      | ||||
|     /** | ||||
|      * The RegEx group that contains the start time of the restriction. | ||||
|      */ | ||||
|     private static final int RESTRICTION_TIME_START_GROUP = 2; | ||||
|      | ||||
|     /** | ||||
|      * The RegEx group that contains the end time of the restriction. | ||||
|      */ | ||||
|     private static final int RESTRICTION_TIME_END_GROUP = 3; | ||||
|      | ||||
|     /** | ||||
|      * A list of DayOfWeek items that make up weekdays. | ||||
|      */ | ||||
|     private static final List<DayOfWeek> RESTRICTION_WEEKDAYS = Arrays.asList( | ||||
|             DayOfWeek.MONDAY, | ||||
|             DayOfWeek.TUESDAY, | ||||
|             DayOfWeek.WEDNESDAY, | ||||
|             DayOfWeek.THURSDAY, | ||||
|             DayOfWeek.FRIDAY | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * A list of DayOfWeek items that make up weekends. | ||||
|      */ | ||||
|     private static final List<DayOfWeek> RESTRICTION_WEEKEND = Arrays.asList( | ||||
|             DayOfWeek.SATURDAY, | ||||
|             DayOfWeek.SUNDAY | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * A list of DayOfWeek items that make up all days of the week. | ||||
|      */ | ||||
|     private static final List<DayOfWeek> RESTRICTION_ALL_DAYS = Arrays.asList( | ||||
|             DayOfWeek.MONDAY, | ||||
|             DayOfWeek.TUESDAY, | ||||
|             DayOfWeek.WEDNESDAY, | ||||
|             DayOfWeek.THURSDAY, | ||||
|             DayOfWeek.FRIDAY, | ||||
|             DayOfWeek.SATURDAY, | ||||
|             DayOfWeek.SUNDAY | ||||
|     ); | ||||
|      | ||||
|     /** | ||||
|      * Parse the provided string containing one or more restrictions into | ||||
|      * a list of objects. | ||||
|      *  | ||||
|      * @param restrictionString | ||||
|      *     The string that should contain one or more semicolon-separated | ||||
|      *     restriction periods. | ||||
|      *  | ||||
|      * @return  | ||||
|      *     A list of objects parsed from the string. | ||||
|      */ | ||||
|     public static List<DailyRestriction> parseString(String restrictionString) { | ||||
|          | ||||
|         List<DailyRestriction> restrictions = new ArrayList<>(); | ||||
|         Matcher restrictionMatcher = RESTRICTION_REGEX.matcher(restrictionString); | ||||
|          | ||||
|         // Loop through RegEx matches | ||||
|         while (restrictionMatcher.find()) { | ||||
|              | ||||
|             // Pull the day string, start time, and end time | ||||
|             String dayString = restrictionMatcher.group(RESTRICTION_DAY_GROUP); | ||||
|             String startTimeString = restrictionMatcher.group(RESTRICTION_TIME_START_GROUP); | ||||
|             String endTimeString = restrictionMatcher.group(RESTRICTION_TIME_END_GROUP); | ||||
|             LocalTime startTime, endTime; | ||||
|              | ||||
|             // We must always have a value for the day. | ||||
|             if (dayString == null || dayString.isEmpty()) | ||||
|                 continue; | ||||
|                          | ||||
|             // Convert the start and end time strings to LocalTime values. | ||||
|             DateTimeFormatter hourFormat = DateTimeFormatter.ofPattern("HHmm"); | ||||
|              | ||||
|             // If start time is empty, assume the start of the day. | ||||
|             if (startTimeString == null || startTimeString.isEmpty()) | ||||
|                 startTime = LocalTime.of(0, 0, 0); | ||||
|              | ||||
|             // Otherwise, parse out the start time. | ||||
|             else | ||||
|                 startTime = LocalTime.parse(startTimeString, hourFormat); | ||||
|              | ||||
|             // If end time is empty, assume the end of the day. | ||||
|             if (endTimeString == null || endTimeString.isEmpty()) | ||||
|                 endTime = LocalTime.of(23, 59, 59); | ||||
|              | ||||
|             // Otherwise, parse out the end time. | ||||
|             else | ||||
|                 endTime = LocalTime.parse(endTimeString, hourFormat); | ||||
|              | ||||
|             // Based on value of day string, add the appropriate entry. | ||||
|             switch(dayString) { | ||||
|                 // All days of the week. | ||||
|                 case "*": | ||||
|                     restrictions.add(new DailyRestriction(RESTRICTION_ALL_DAYS, startTime, endTime)); | ||||
|                     break; | ||||
|                      | ||||
|                 // Weekdays only. | ||||
|                 case "wd": | ||||
|                     restrictions.add(new DailyRestriction(RESTRICTION_WEEKDAYS, startTime, endTime)); | ||||
|                     break; | ||||
|                  | ||||
|                 // Weekend days only. | ||||
|                 case "we": | ||||
|                     restrictions.add(new DailyRestriction(RESTRICTION_WEEKEND, startTime, endTime)); | ||||
|                     break; | ||||
|                      | ||||
|                 // A specific day of the week. | ||||
|                 default: | ||||
|                     restrictions.add(new DailyRestriction(DayOfWeek.of(Integer.parseInt(dayString)), startTime, endTime)); | ||||
|                      | ||||
|             } | ||||
|              | ||||
|         } | ||||
|          | ||||
|         // Return the list of restrictions | ||||
|         return restrictions; | ||||
|          | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,77 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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.apache.guacamole.host; | ||||
|  | ||||
| import inet.ipaddr.HostName; | ||||
| import inet.ipaddr.HostNameException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * A utility class that parses a string for a set of IPv4 or IPv6 addresses, | ||||
|  * or hostnames, splitting the string into a list of components. | ||||
|  */ | ||||
| public class HostRestrictionParser { | ||||
|      | ||||
|     /** | ||||
|      * The logger for this class. | ||||
|      */ | ||||
|     private static final Logger LOGGER = LoggerFactory.getLogger(HostRestrictionParser.class); | ||||
|      | ||||
|     /** | ||||
|      * Parse the provided string into a List of HostName objects, validating | ||||
|      * that each item is an IP address, subnet, and/or DNS name. | ||||
|      *  | ||||
|      * @param hostString | ||||
|      *     The string that contains a semi-colon-separated list of items to | ||||
|      *     parse. | ||||
|      *  | ||||
|      * @return | ||||
|      *     A List of HostName objects parsed from the provided string. | ||||
|      */ | ||||
|     public static List<HostName> parseHostList(String hostString) { | ||||
|          | ||||
|         List<HostName> addressList = new ArrayList<>(); | ||||
|          | ||||
|         if (hostString == null || hostString.isEmpty()) | ||||
|             return addressList; | ||||
|          | ||||
|         // First split the string by semicolons and process each entry | ||||
|         for (String host : hostString.split(";")) { | ||||
|              | ||||
|             HostName hostName = new HostName(host); | ||||
|             try { | ||||
|                 hostName.validate(); | ||||
|                 addressList.add(hostName); | ||||
|             } | ||||
|             catch (HostNameException e) { | ||||
|                 LOGGER.warn("Invalid host name or IP: {}", host); | ||||
|                 LOGGER.debug("HostNameException.", e.getMessage()); | ||||
|             } | ||||
|              | ||||
|         } | ||||
|          | ||||
|         return addressList; | ||||
|          | ||||
|     } | ||||
|      | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Config block which registers restrict-specific field types. | ||||
|  */ | ||||
| angular.module('guacRestrict').config(['formServiceProvider', | ||||
|         function guacRestrictConfig(formServiceProvider) { | ||||
|  | ||||
|     // Define the time restriction field | ||||
|     formServiceProvider.registerFieldType('GUAC_TIME_RESTRICTION', { | ||||
|         module      : 'guacRestrict', | ||||
|         controller  : 'timeRestrictionFieldController', | ||||
|         templateUrl : 'app/ext/restrict/templates/timeRestrictionField.html' | ||||
|     }); | ||||
|      | ||||
|     // Define the host restriction field | ||||
|     formServiceProvider.registerFieldType('GUAC_HOST_RESTRICTION', { | ||||
|         module      : 'guacRestrict', | ||||
|         controller  : 'hostRestrictionFieldController', | ||||
|         templateUrl : 'app/ext/restrict/templates/hostRestrictionField.html' | ||||
|     }); | ||||
|  | ||||
| }]); | ||||
| @@ -0,0 +1,170 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for host restriction fields, which are used to configure a | ||||
|  * hostname, IP address, or CIDR range, that this restriction applies to. | ||||
|  */ | ||||
| angular.module('guacRestrict').controller('hostRestrictionFieldController', ['$scope', '$injector', | ||||
|     function hostRestrictionFieldController($scope, $injector) { | ||||
|         | ||||
|     // Required types | ||||
|     const HostRestrictionEntry = $injector.get('HostRestrictionEntry'); | ||||
|          | ||||
|     /** | ||||
|      * Options which dictate the behavior of the input field model, as defined | ||||
|      * by https://docs.angularjs.org/api/ng/directive/ngModelOptions | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     $scope.modelOptions = { | ||||
|          | ||||
|         /** | ||||
|          * Space-delimited list of events on which the model will be updated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         updateOn : 'blur', | ||||
|  | ||||
|         /** | ||||
|          * The time zone to use when reading/writing the Date object of the | ||||
|          * model. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         timezone : 'UTC' | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * The restrictions, as objects, that are used by the HTML template to | ||||
|      * present the restrictions to the user via the web interface. | ||||
|      *  | ||||
|      * @type HostRestrictionEntry[] | ||||
|      */ | ||||
|     $scope.restrictions = []; | ||||
|      | ||||
|     /** | ||||
|      * Remove the current entry from the list. | ||||
|      *  | ||||
|      * @param {HostRestrictionEntry} entry | ||||
|      *     A restriction entry. | ||||
|      */ | ||||
|     $scope.removeEntry = function removeEntry(entry) { | ||||
|         if (entry === null || entry.$$hashKey === '') { | ||||
|             return; | ||||
|         } | ||||
|         for (let i = 0; i < $scope.restrictions.length; i++) { | ||||
|             if ($scope.restrictions[i].$$hashKey === entry.$$hashKey) { | ||||
|                 $scope.restrictions.splice(i,1); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Add an empty entry to the restriction list. | ||||
|      */ | ||||
|     $scope.addEntry = function addEntry() { | ||||
|         $scope.restrictions.push(new HostRestrictionEntry()); | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Parse the provided string into an array containing the objects that | ||||
|      * represent each of entries that can then be displayed as a more | ||||
|      * user-friendly field. | ||||
|      *  | ||||
|      * @param {String} restrString | ||||
|      *     The string that contains the restrictions, un-parsed and as stored | ||||
|      *     in the underlying field. | ||||
|      *      | ||||
|      * @returns {HostRestrictionEntry[]} | ||||
|      *     An array of objects that represents each of the entries as parsed | ||||
|      *     out of the string field, and which can be interpreted by the | ||||
|      *     AngularJS field for display. | ||||
|      */ | ||||
|     const parseRestrictions = function parseRestrictions(restrString) { | ||||
|          | ||||
|         var restrictions = []; | ||||
|          | ||||
|         // If the string is null or empty, just return an empty array | ||||
|         if (restrString === null || restrString === "") | ||||
|             return restrictions; | ||||
|          | ||||
|         // Set up the RegEx and split the string using the separator. | ||||
|         var restrArray = restrString.split(";"); | ||||
|          | ||||
|         // Loop through split string and process each item | ||||
|         for (let i = 0; i < restrArray.length; i++) { | ||||
|             var entry = new HostRestrictionEntry(); | ||||
|             entry.host = restrArray[i]; | ||||
|             restrictions.push(entry); | ||||
|         } | ||||
|          | ||||
|         return restrictions; | ||||
|          | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Parse the restrictions in the field into a string that can be stored | ||||
|      * in an underlying module. | ||||
|      *  | ||||
|      * @param {HostRestrictionEntry[]} restrictions | ||||
|      *     The array of restrictions that will be converted to a string. | ||||
|      *  | ||||
|      * @returns {String} | ||||
|      *     The string containing the restriction data that can be stored in e.g. | ||||
|      *     a database. | ||||
|      */ | ||||
|     const storeRestrictions = function storeRestrictions(restrictions) { | ||||
|         // If there are no members of the array, just return an empty string. | ||||
|         if (restrictions === null || restrictions.length < 1) | ||||
|             return ''; | ||||
|          | ||||
|         var restrString = ''; | ||||
|         for (let i = 0; i < restrictions.length; i++) { | ||||
|             // If any of the properties are not defined, skip this one. | ||||
|             if (!Object.hasOwn(restrictions[i], 'host') | ||||
|                     || restrictions[i].host === null) | ||||
|                 continue; | ||||
|              | ||||
|             // If this is not the first item, then add a semi-colon separator | ||||
|             if (restrString.length > 0) | ||||
|                 restrString += ';'; | ||||
|              | ||||
|             // Add the current host to the list | ||||
|             restrString += restrictions[i].host; | ||||
|         } | ||||
|  | ||||
|         return restrString; | ||||
|          | ||||
|     }; | ||||
|      | ||||
|     // Update the field when the model changes. | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.restrictions = parseRestrictions(model); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when web form is changed | ||||
|     $scope.$watch('restrictions', function restrictionsChanged(restrictions) { | ||||
|         $scope.model = storeRestrictions(restrictions); | ||||
|     }, true); | ||||
|          | ||||
| }]); | ||||
| @@ -0,0 +1,223 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Controller for time restriction fields, which are used to select weekday and | ||||
|  * time restrictions that apply to user logins and connections. | ||||
|  */ | ||||
| angular.module('guacRestrict').controller('timeRestrictionFieldController', ['$scope', '$injector', | ||||
|         function timeRestrictionFieldController($scope, $injector) { | ||||
|      | ||||
|     // Required types | ||||
|     const TimeRestrictionEntry = $injector.get('TimeRestrictionEntry'); | ||||
|      | ||||
|     /** | ||||
|      * Options which dictate the behavior of the input field model, as defined | ||||
|      * by https://docs.angularjs.org/api/ng/directive/ngModelOptions | ||||
|      * | ||||
|      * @type Object.<String, String> | ||||
|      */ | ||||
|     $scope.modelOptions = { | ||||
|          | ||||
|         /** | ||||
|          * Space-delimited list of events on which the model will be updated. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         updateOn : 'blur' | ||||
|  | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * The restrictions, as objects, that are used by the HTML template to | ||||
|      * present the restrictions to the user via the web interface. | ||||
|      *  | ||||
|      * @type TimeRestrictionEntry[] | ||||
|      */ | ||||
|     $scope.restrictions = []; | ||||
|  | ||||
|     /** | ||||
|      * Map of weekday identifier to display name. | ||||
|      */ | ||||
|     $scope.weekDays = [ | ||||
|         { id : '1', day : 'Monday' }, | ||||
|         { id : '2', day : 'Tuesday' }, | ||||
|         { id : '3', day : 'Wednesday' }, | ||||
|         { id : '4', day : 'Thursday' }, | ||||
|         { id : '5', day : 'Friday' }, | ||||
|         { id : '6', day : 'Saturday' }, | ||||
|         { id : '7', day : 'Sunday' }, | ||||
|         { id : '*', day : 'All days' }, | ||||
|         { id : 'wd', day: 'Week days' }, | ||||
|         { id : 'we', day: 'Week end' } | ||||
|     ]; | ||||
|      | ||||
|     /** | ||||
|      * Remove the current entry from the list. | ||||
|      *  | ||||
|      * @param {TimeRestrictionEntry} entry | ||||
|      *     A restriction entry. | ||||
|      */ | ||||
|     $scope.removeEntry = function removeEntry(entry) { | ||||
|         if (entry === null || entry.$$hashKey === '') { | ||||
|             return; | ||||
|         } | ||||
|         for (let i = 0; i < $scope.restrictions.length; i++) { | ||||
|             if ($scope.restrictions[i].$$hashKey === entry.$$hashKey) { | ||||
|                 $scope.restrictions.splice(i,1); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Add an empty entry to the restriction list. | ||||
|      */ | ||||
|     $scope.addEntry = function addEntry() { | ||||
|         $scope.restrictions.push(new TimeRestrictionEntry()); | ||||
|     }; | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * Parse the provided string into an array containing the objects that | ||||
|      * represent each of entries that can then be displayed as a more | ||||
|      * user-friendly field. | ||||
|      *  | ||||
|      * @param {String} restrString | ||||
|      *     The string that contains the restrictions, un-parsed and as stored | ||||
|      *     in the underlying field. | ||||
|      *      | ||||
|      * @returns {TimeRestrictionEntry[]} | ||||
|      *     An array of objects that represents each of the entries as parsed | ||||
|      *     out of the string field, and which can be interpreted by the | ||||
|      *     AngularJS field for display. | ||||
|      */ | ||||
|     const parseRestrictions = function parseRestrictions(restrString) { | ||||
|          | ||||
|         var restrictions = []; | ||||
|          | ||||
|         // If the string is null or empty, just return an empty array | ||||
|         if (restrString === null || restrString === "") | ||||
|             return restrictions; | ||||
|          | ||||
|         // Set up the RegEx and split the string using the separator. | ||||
|         const restrictionRegex = new RegExp('^([1-7*]|(?:[w][ed]))(?::((?:[01][0-9]|2[0-3])[0-5][0-9])\-((?:[01][0-9]|2[0-3])[0-5][0-9]))$'); | ||||
|         var restrArray = restrString.split(";"); | ||||
|  | ||||
|         // Loop through split string and process each item | ||||
|         for (let i = 0; i < restrArray.length; i++) { | ||||
|              | ||||
|             // Test if our regex matches | ||||
|             if (restrictionRegex.test(restrArray[i])) { | ||||
|                 var currArray = restrArray[i].match(restrictionRegex); | ||||
|                 var entry = new TimeRestrictionEntry(); | ||||
|                 entry.weekDay = '' + currArray[1]; | ||||
|                 entry.startTime = new Date(Date.UTC(1970, 1, 1, parseInt(currArray[2].slice(0,2)), parseInt(currArray[2].slice(2)), 0, 0)); | ||||
|                 entry.endTime = new Date(Date.UTC(1970, 1, 1, parseInt(currArray[3].slice(0,2)), parseInt(currArray[3].slice(2)), 0, 0)); | ||||
|                 restrictions.push(entry); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         return restrictions; | ||||
|          | ||||
|     }; | ||||
|      | ||||
|     /** | ||||
|      * Parse the restrictions in the field into a string that can be stored | ||||
|      * in an underlying module. | ||||
|      *  | ||||
|      * @param {TimeRestrictionEntry[]} restrictions | ||||
|      *     The array of restrictions that will be converted to a string. | ||||
|      *  | ||||
|      * @returns {String} | ||||
|      *     The string containing the restriction data that can be stored in e.g. | ||||
|      *     a database. | ||||
|      */ | ||||
|     const storeRestrictions = function storeRestrictions(restrictions) { | ||||
|         // If there are no members of the array, just return an empty string. | ||||
|         if (restrictions === null || restrictions.length < 1) | ||||
|             return ''; | ||||
|          | ||||
|         var restrString = ''; | ||||
|         for (let i = 0; i < restrictions.length; i++) { | ||||
|             // If any of the properties are not defined, skip this one. | ||||
|             if (!Object.hasOwn(restrictions[i], 'weekDay') | ||||
|                     || restrictions[i].weekDay === null | ||||
|                     || !Object.hasOwn(restrictions[i], 'startTime') | ||||
|                     || restrictions[i].startTime === null | ||||
|                     || !(restrictions[i].startTime instanceof Date) | ||||
|                     || !Object.hasOwn(restrictions[i], 'endTime')  | ||||
|                     || restrictions[i].endTime === null | ||||
|                     || !(restrictions[i].endTime instanceof Date)) | ||||
|                 continue; | ||||
|              | ||||
|             // If this is not the first item, then add a semi-colon separator | ||||
|             if (restrString.length > 0) | ||||
|                 restrString += ';'; | ||||
|              | ||||
|             // Add the weekday component of the restriction, insuring it is a string. | ||||
|             var currString = '' + restrictions[i].weekDay; | ||||
|             currString += ':'; | ||||
|                  | ||||
|             // Retrieve startTime hours component and add it, adding leading zero if required. | ||||
|             startHours = restrictions[i].startTime.getUTCHours(); | ||||
|             if (startHours !== null && startHours < 10) | ||||
|                 startHours = '0' + startHours; | ||||
|             currString += startHours; | ||||
|  | ||||
|             // Retrieve startTime minutes component and add it, adding leading zero if required. | ||||
|             startMins = restrictions[i].startTime.getUTCMinutes(); | ||||
|             if (startMins !== null && startMins < 10) | ||||
|                 startMins = '0' + startMins; | ||||
|             currString += startMins; | ||||
|              | ||||
|             currString += '-'; | ||||
|              | ||||
|             // Retrieve endTime hours component and add it, adding leading zero if required. | ||||
|             endHours = restrictions[i].endTime.getUTCHours(); | ||||
|             if (endHours !== null && endHours < 10) | ||||
|                 endHours = '0' + endHours; | ||||
|             currString += endHours; | ||||
|  | ||||
|             // Retrieve endTime minutes component and add it, adding leading zero if required. | ||||
|             endMins = restrictions[i].endTime.getUTCMinutes(); | ||||
|             if (endMins < 10) | ||||
|                 endMins = '0' + endMins; | ||||
|             currString += endMins; | ||||
|              | ||||
|             // Add the newly-created string to the overall restriction string. | ||||
|             restrString += currString; | ||||
|         } | ||||
|  | ||||
|         return restrString; | ||||
|          | ||||
|     }; | ||||
|      | ||||
|     // Update the field when the model changes. | ||||
|     $scope.$watch('model', function modelChanged(model) { | ||||
|         $scope.restrictions = parseRestrictions(model); | ||||
|     }); | ||||
|  | ||||
|     // Update string value in model when web form is changed | ||||
|     $scope.$watch('restrictions', function restrictionsChanged(restrictions) { | ||||
|         $scope.model = storeRestrictions(restrictions); | ||||
|     }, true); | ||||
|          | ||||
| }]); | ||||
| @@ -0,0 +1,29 @@ | ||||
| { | ||||
|  | ||||
|     "guacamoleVersion" : "1.6.0", | ||||
|  | ||||
|     "name"      : "Restriction Authentication Backend", | ||||
|     "namespace" : "restrict", | ||||
|  | ||||
|     "authProviders" : [ | ||||
|         "org.apache.guacamole.auth.restrict.RestrictionAuthenticationProvider" | ||||
|     ], | ||||
|  | ||||
|     "translations" : [ | ||||
|         "translations/en.json" | ||||
|     ], | ||||
|      | ||||
|     "js" : [ | ||||
|         "restrict.min.js" | ||||
|     ], | ||||
|  | ||||
|     "css" : [ | ||||
|         "restrict.min.css" | ||||
|     ], | ||||
|  | ||||
|     "resources" : { | ||||
|         "templates/hostRestrictionField.html" : "text/html", | ||||
|         "templates/timeRestrictionField.html" : "text/html" | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
| @@ -0,0 +1,29 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Module which provides handling for additional login and connection | ||||
|  * restrictions. | ||||
|  */ | ||||
| angular.module('guacRestrict', [ | ||||
|     'form' | ||||
| ]); | ||||
|  | ||||
| // Ensure the guacRestrict module is loaded along with the rest of the app | ||||
| angular.module('index').requires.push('guacRestrict'); | ||||
| @@ -0,0 +1,36 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
| .restrictionList { | ||||
|     border: 0; | ||||
| } | ||||
|  | ||||
| button.restrictionListButton { | ||||
|     font-size: 0.75em; | ||||
| } | ||||
|  | ||||
| img.restrictionListHeader { | ||||
|     width: 0.75em; | ||||
|     height: 0.75em; | ||||
| } | ||||
|  | ||||
| img.restrictionListItem { | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| <div class="hostField"> | ||||
|     <table class="restrictionList" ng-show="restrictions !== null && restrictions.length > 0"> | ||||
|         <tr> | ||||
|             <th>{{ 'RESTRICT.TABLE_HEADER_HOST' | translate }}</th> | ||||
|             <th> </th> | ||||
|         </tr> | ||||
|         <tr class="restrictionListItem" ng-repeat="entry in restrictions"> | ||||
|             <td> | ||||
|                 <input type="text" ng-model="entry.host" ng-model-options="modelOptions"> | ||||
|             </td> | ||||
|             <td> | ||||
|                 <img class="restrictionListItem" | ||||
|                      src="images/x-red.svg" | ||||
|                      alt="Remove entry" | ||||
|                      ng-click="removeEntry(entry)"> | ||||
|             </td> | ||||
|         </tr> | ||||
|     </table> | ||||
|     <button ng-click="addEntry()" | ||||
|             class="restrictionListButton"> | ||||
|         {{ 'RESTRICT.ACTION_ADD_ENTRY' | translate }} | ||||
|     </button> | ||||
| </div> | ||||
| @@ -0,0 +1,38 @@ | ||||
| <div class="timeRestrictionField"> | ||||
|     <table class="restrictionList" ng-show="restrictions !== null && restrictions.length > 0"> | ||||
|         <tr> | ||||
|             <th>{{ 'RESTRICT.TABLE_HEADER_DAY' | translate }}</th> | ||||
|             <th>{{ 'RESTRICT.TABLE_HEADER_START_TIME' | translate }}</th> | ||||
|             <th>{{ 'RESTRICT.TABLE_HEADER_END_TIME' | translate }}</th> | ||||
|             <th> </th> | ||||
|         </tr> | ||||
|         <tr class="restrictionListItem" ng-repeat="entry in restrictions"> | ||||
|             <td> | ||||
|                 <select ng-model="entry.weekDay" | ||||
|                         ng-options="weekDay.id as weekDay.day for weekDay in weekDays" | ||||
|                         ng-model-options="modelOptions"> | ||||
|                 </select> | ||||
|             </td> | ||||
|             <td> | ||||
|                 <input type="time" | ||||
|                        ng-model="entry.startTime" | ||||
|                        ng-model-options="modelOptions"> | ||||
|             </td> | ||||
|             <td> | ||||
|                 <input type="time" | ||||
|                        ng-model="entry.endTime" | ||||
|                        ng-model-options="modelOptions"> | ||||
|             </td> | ||||
|             <td> | ||||
|                 <img class="restrictionListItem" | ||||
|                      src="images/x-red.svg" | ||||
|                      alt="Remove entry" | ||||
|                      ng-click="removeEntry(entry)"> | ||||
|             </td> | ||||
|         </tr> | ||||
|     </table> | ||||
|     <button ng-click="addEntry()" | ||||
|             class="restrictionListButton"> | ||||
|         {{ 'RESTRICT.ACTION_ADD_ENTRY' | translate }} | ||||
|     </button> | ||||
| </div> | ||||
| @@ -0,0 +1,67 @@ | ||||
| { | ||||
|  | ||||
|     "DATA_SOURCE_LOGIN_RESTRICTIONS" : { | ||||
|         "NAME" : "Additional Restrictions" | ||||
|     }, | ||||
|      | ||||
|     "CONNECTION_ATTRIBUTES" : { | ||||
|          | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_ALLOWED" : "Hosts from which connection may be accessed:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_DENIED"  : "Hosts from which connection may not be accessed:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED"  : "Times connection is allowed to be used:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED"   : "Times connection may not be used:", | ||||
|          | ||||
|         "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Connection Restrictions" | ||||
|          | ||||
|     }, | ||||
|      | ||||
|     "CONNECTION_GROUP_ATTRIBUTES" : { | ||||
|          | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_ALLOWED" : "Hosts from which connection group may be accessed:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_DENIED"  : "Hosts from which connection group may not be accessed:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED"  : "Times connection group is allowed to be used:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED"   : "Times connection group may not be used:", | ||||
|          | ||||
|         "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Connection Restrictions" | ||||
|          | ||||
|     }, | ||||
|      | ||||
|     "RESTRICT" : { | ||||
|          | ||||
|         "ACTION_ADD_ENTRY" : "Add Entry", | ||||
|          | ||||
|         "ERROR_CONNECTION_NOT_ALLOWED_NOW"       : "The connection is not available at this time.", | ||||
|         "ERROR_CONNECTION_NOT_ALLOWED_FROM_HOST" : "The connection is not allowed from this host.", | ||||
|         "ERROR_USER_LOGIN_NOT_ALLOWED_NOW"       : "The login for this user is not allowed at this time.", | ||||
|         "ERROR_USER_LOGIN_NOT_ALLOWED_FROM_HOST" : "The login for this user is not allowed from this host.", | ||||
|          | ||||
|         "TABLE_HEADER_DAY"        : "Day", | ||||
|         "TABLE_HEADER_END_TIME"   : "End Time", | ||||
|         "TABLE_HEADER_HOST"       : "Host", | ||||
|         "TABLE_HEADER_START_TIME" : "Start Time" | ||||
|          | ||||
|     }, | ||||
|      | ||||
|     "USER_ATTRIBUTES" : { | ||||
|          | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_ALLOWED" : "Hosts from which user can log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_DENIED"  : "Hosts from which user may not log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED"  : "Times user is allowed to log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED"   : "Times user is denied from log in:", | ||||
|          | ||||
|         "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Login Restrictions" | ||||
|          | ||||
|     }, | ||||
|      | ||||
|     "USER_GROUP_ATTRIBUTES" : { | ||||
|          | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_ALLOWED" : "Hosts from which members may log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_HOSTS_DENIED"  : "Hosts from which members may not log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED"  : "Times members are allowed to log in:", | ||||
|         "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED"   : "Times members are denied from log in:", | ||||
|          | ||||
|         "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Login Restrictions" | ||||
|          | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the HostRestrictionEntry class definition. | ||||
|  */ | ||||
| angular.module('guacRestrict').factory('HostRestrictionEntry', [ | ||||
|         function defineHostRestrictionEntry() { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new HostRestrictionEntry, initializing the properties of that | ||||
|      * HostRestrictionEntry with the corresponding properties of the given | ||||
|      * template. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {HostRestrictionEntry|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     HostRestrictionEntry. | ||||
|      */ | ||||
|     var HostRestrictionEntry = function HostRestrictionEntry(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|          | ||||
|         /** | ||||
|          * The IP address, CIDR notation range, or DNS hostname of the host(s) | ||||
|          * specified by this restriction. | ||||
|          * | ||||
|          * @type String | ||||
|          */ | ||||
|         this.host = template.host || ''; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return HostRestrictionEntry; | ||||
|  | ||||
| }]); | ||||
| @@ -0,0 +1,69 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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. | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * Provides the TimeRestrictionEntry class definition. | ||||
|  */ | ||||
| angular.module('guacRestrict').factory('TimeRestrictionEntry', [ | ||||
|         function defineTimeRestrictionEntry() { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new TimeRestrictionEntry, initializing the properties of that | ||||
|      * TimeRestrictionEntry with the corresponding properties of the given | ||||
|      * template. | ||||
|      * | ||||
|      * @constructor | ||||
|      * @param {TimeRestrictionEntry|Object} [template={}] | ||||
|      *     The object whose properties should be copied within the new | ||||
|      *     TimeRestrictionEntry. | ||||
|      */ | ||||
|     var TimeRestrictionEntry = function TimeRestrictionEntry(template) { | ||||
|  | ||||
|         // Use empty object by default | ||||
|         template = template || {}; | ||||
|          | ||||
|         /** | ||||
|          * The numerical representation of the day of the week this restriction | ||||
|          * applies to. | ||||
|          * | ||||
|          * @type Number | ||||
|          */ | ||||
|         this.weekDay = template.weekDay; | ||||
|          | ||||
|         /** | ||||
|          * The hour and minute that this restriction starts, in 24-hour time, | ||||
|          * and with no separator between the hour and minute. | ||||
|          *  | ||||
|          * @type Date | ||||
|          */ | ||||
|         this.startTime = template.startTime; | ||||
|          | ||||
|         /** | ||||
|          * The hour and minute that this restriction ends, in 24-hour time, and | ||||
|          * with no separator between the hour and minute. | ||||
|          *  | ||||
|          * @type Date | ||||
|          */ | ||||
|         this.endTime = template.endTime; | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     return TimeRestrictionEntry; | ||||
|  | ||||
| }]); | ||||
| @@ -28,7 +28,7 @@ | ||||
|     <packaging>jar</packaging> | ||||
|     <version>1.6.0</version> | ||||
|     <name>guacamole-auth-totp</name> | ||||
|     <url>http://guacamole.incubator.apache.org/</url> | ||||
|     <url>http://guacamole.apache.org/</url> | ||||
|  | ||||
|     <parent> | ||||
|         <groupId>org.apache.guacamole</groupId> | ||||
|   | ||||
| @@ -47,6 +47,7 @@ | ||||
|         <module>guacamole-auth-json</module> | ||||
|         <module>guacamole-auth-ldap</module> | ||||
|         <module>guacamole-auth-quickconnect</module> | ||||
|         <module>guacamole-auth-restrict</module> | ||||
|         <module>guacamole-auth-sso</module> | ||||
|         <module>guacamole-auth-totp</module> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user