diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLAuthenticationProviderModule.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLAuthenticationProviderModule.java index ce13bfed20..0498380c6c 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/MySQLAuthenticationProviderModule.java @@ -84,6 +84,10 @@ public MySQLAuthenticationProviderModule(MySQLEnvironment environment) myBatisProperties.setProperty("mybatis.pooled.pingEnabled", "true"); myBatisProperties.setProperty("mybatis.pooled.pingQuery", "SELECT 1"); + // Set whether public key retrieval from the server is allowed + driverProperties.setProperty("allowPublicKeyRetrieval", + environment.getMYSQLAllowPublicKeyRetrieval() ? "true" : "false"); + // Use UTF-8 in database driverProperties.setProperty("characterEncoding", "UTF-8"); @@ -121,10 +125,22 @@ public MySQLAuthenticationProviderModule(MySQLEnvironment environment) if (clientPassword != null) driverProperties.setProperty("clientCertificateKeyStorePassword", clientPassword); - + // Get the MySQL-compatible driver to use. mysqlDriver = environment.getMySQLDriver(); + // Set the path to the server public key, if any + // Note that the property name casing is slightly different for MySQL + // and MariaDB drivers. See + // https://dev.mysql.com/doc/connector-j/en/connector-j-connp-props-security.html#cj-conn-prop_serverRSAPublicKeyFile + // and https://mariadb.com/kb/en/about-mariadb-connector-j/#infrequently-used-parameters + String publicKeyFile = environment.getMYSQLServerRSAPublicKeyFile(); + if (publicKeyFile != null) + driverProperties.setProperty( + mysqlDriver == MySQLDriver.MYSQL + ? "serverRSAPublicKeyFile" : "serverRsaPublicKeyFile", + publicKeyFile); + // If timezone is present, set it. TimeZone serverTz = environment.getServerTimeZone(); if (serverTz != null) diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java index 4fb7b6c8d0..19abdbdbfa 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLEnvironment.java @@ -443,4 +443,35 @@ public boolean enforceAccessWindowsForActiveSessions() throws GuacamoleException ); } + /** + * Returns the absolute path to the public key for the server being connected to, + * if any, or null if the configuration property is unset. + * + * @return + * The absolute path to the public key for the server being connected to. + * + * @throws GuacamoleException + * If an error occurs retrieving the configuration value. + */ + public String getMYSQLServerRSAPublicKeyFile() throws GuacamoleException { + return getProperty(MySQLGuacamoleProperties.MYSQL_SERVER_RSA_PUBLIC_KEY_FILE); + } + + /** + * Returns true if the database server public key should be automatically + * retrieved from the MySQL server, or false otherwise. + * + * @return + * Whether the database server public key should be automatically + * retrieved from the MySQL server. + * + * @throws GuacamoleException + * If an error occurs retrieving the configuration value. + */ + public boolean getMYSQLAllowPublicKeyRetrieval() throws GuacamoleException { + return getProperty( + MySQLGuacamoleProperties.MYSQL_ALLOW_PUBLIC_KEY_RETRIEVAL, + false); + } + } diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java index c7385db817..5f70bf1d63 100644 --- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java +++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-mysql/src/main/java/org/apache/guacamole/auth/mysql/conf/MySQLGuacamoleProperties.java @@ -302,5 +302,28 @@ private MySQLGuacamoleProperties() {} public String getName() { return "mysql-batch-size"; } }; - + + /** + * The absolute path to the public key for the server being connected to, if any. + */ + public static final StringGuacamoleProperty MYSQL_SERVER_RSA_PUBLIC_KEY_FILE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "mysql-server-rsa-public-key-file"; } + + }; + + /** + * Whether or not the server public key should be automatically retreived from + * the MySQL server. + */ + public static final BooleanGuacamoleProperty MYSQL_ALLOW_PUBLIC_KEY_RETRIEVAL = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "mysql-allow-public-key-retrieval"; } + + }; + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java index c41114c027..20d465803d 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/ConnectedLDAPConfiguration.java @@ -223,5 +223,10 @@ public String getMemberAttribute() throws GuacamoleException { public MemberAttributeType getMemberAttributeType() throws GuacamoleException { return config.getMemberAttributeType(); } + + @Override + public boolean getNestedGroups() throws GuacamoleException { + return config.getNestedGroups(); + } } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java index 2179643821..1cbf41ed02 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/DefaultLDAPConfiguration.java @@ -151,4 +151,9 @@ public MemberAttributeType getMemberAttributeType() return MemberAttributeType.DN; } + @Override + public boolean getNestedGroups() { + return false; + } + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java index 5ffeb203b8..1cfffa5898 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/EnvironmentLDAPConfiguration.java @@ -234,4 +234,12 @@ public MemberAttributeType getMemberAttributeType() ); } + @Override + public boolean getNestedGroups() throws GuacamoleException { + return environment.getProperty( + LDAPGuacamoleProperties.LDAP_NESTED_GROUPS, + DEFAULT.getNestedGroups() + ); + } + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java index bddccd871f..88a20dfe38 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/JacksonLDAPConfiguration.java @@ -204,6 +204,13 @@ public class JacksonLDAPConfiguration implements LDAPConfiguration { @JsonProperty("member-attribute-type") private String memberAttributeType; + /** + * The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_NESTED_GROUPS}. + * If not set within the YAML, this will be false. + */ + @JsonProperty("nested-groups") + private Boolean nestedGroups; + /** * The default configuration options for all parameters. */ @@ -434,6 +441,11 @@ public String getMemberAttribute() throws GuacamoleException { return withDefault(memberAttribute, defaultConfig::getMemberAttribute); } + @Override + public boolean getNestedGroups() throws GuacamoleException { + return withDefault(nestedGroups, defaultConfig::getNestedGroups); + } + @Override public MemberAttributeType getMemberAttributeType() throws GuacamoleException { return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE, diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java index abbf9103a1..4c56d4400c 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPConfiguration.java @@ -334,4 +334,16 @@ public interface LDAPConfiguration { */ MemberAttributeType getMemberAttributeType() throws GuacamoleException; + /** + * Returns whether nested groups should be included in group membership. + * + * @return + * Whether to search in nested groups. + * + * @throws GuacamoleException + * If the configuration information determining whether nested + * groups should be used cannot be retrieved. + */ + boolean getNestedGroups() throws GuacamoleException; + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java index 7349356b94..10c0693f77 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/conf/LDAPGuacamoleProperties.java @@ -307,4 +307,15 @@ private LDAPGuacamoleProperties() {} }; + /** + * Whether or not to search nested groups. + */ + public static final BooleanGuacamoleProperty LDAP_NESTED_GROUPS = + new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "ldap-nested-groups"; } + + }; + } diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java index 89049a00bf..b171374ac7 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/group/UserGroupService.java @@ -27,10 +27,12 @@ import java.util.Map; import java.util.Set; import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.entry.Value; import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; import org.apache.directory.api.ldap.model.filter.AndNode; import org.apache.directory.api.ldap.model.filter.EqualityNode; import org.apache.directory.api.ldap.model.filter.ExprNode; +import org.apache.directory.api.ldap.model.filter.ExtensibleNode; import org.apache.directory.api.ldap.model.filter.NotNode; import org.apache.directory.api.ldap.model.name.Dn; import org.apache.guacamole.auth.ldap.conf.MemberAttributeType; @@ -55,6 +57,11 @@ public class UserGroupService { */ private static final Logger logger = LoggerFactory.getLogger(UserGroupService.class); + /** + * Constant for nested LDAP group matching in Active Directory + */ + private static final String LDAP_GROUP_NESTED_MATCHING_OID = "1.2.840.113556.1.4.1941"; + /** * Service for executing LDAP queries. */ @@ -225,14 +232,36 @@ public List getParentUserGroupEntries(ConnectedLDAPConfiguration config, groupAttributes.add(memberAttribute); // Get all groups the user is a member of starting at the groupBaseDN, - // excluding guacConfigGroups + // excluding guacConfigGroups and evaluating nested groups + // (if enabled). + + ExprNode groupFilter = config.getGroupSearchFilter(); + String filterValue = userIDorDN; + + if (config.getNestedGroups()) { + + // Add support for nested groups using LDAP_MATCHING_RULE_IN_CHAIN + // (memberOf:1.2.840.113556.1.4.1941:=) + // Matching rule OID for LDAP_MATCHING_RULE_IN_CHAIN + // ** This possibly only supports Active Directory ** + ExtensibleNode node = new ExtensibleNode("member"); + filterValue = null; + + // Explicitly set the matching rule ID and dnAttributes + node.setMatchingRuleId(LDAP_GROUP_NESTED_MATCHING_OID); + node.setDnAttributes(false); + node.setValue(new Value(userIDorDN)); + groupFilter = new AndNode( + groupFilter, node + ); + } return queryService.search( config, config.getLDAPConnection(), groupBaseDN, - getGroupSearchFilter(config), + groupFilter, Collections.singleton(memberAttribute), - userIDorDN, + filterValue, groupAttributes ); diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/Restrictable.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/Restrictable.java index ff1acf7450..5feb21477a 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/Restrictable.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/Restrictable.java @@ -27,6 +27,65 @@ */ public interface Restrictable extends Attributes { + /** + * The name of the attribute that contains the absolute date and time after + * which this restrictable object may be used. If this attribute is present + * access to to this object will be denied at any time prior to the parsed + * value of this attribute, regardless of what other restrictions may be + * present to allow access to the object at certain days/times of the week + * or from certain hosts. + */ + public static final String RESTRICT_TIME_AFTER_ATTRIBUTE_NAME = "guac-restrict-time-after"; + + /** + * The name of the attribute that contains a list of weekdays and times (UTC) + * that this restrictable object can be used. The presence of values within + * this attribute will automatically restrict use of the object 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 the absolute date and time before + * which use of this restrictable object may be used. If this attribute is + * present use of the object will be denied at any time after the parsed + * value of this attribute, regardless of the presence of other restrictions + * that may allow access at certain days/times of the week or from certain + * hosts. + */ + public static final String RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME = "guac-restrict-time-before"; + + /** + * The name of the attribute that contains a list of weekdays and times (UTC) + * that this restrictable object cannot be used. 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 this + * restrictable object may be used. The presence of this attribute will + * restrict use 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 this + * restrictable object may not be used. The presence of this attribute, + * absent the guac-restrict-hosts-allowed attribute, will allow use 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"; + /** * Return the restriction state for this restrictable object at the * current date and time. By default returns an implicit denial. diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProvider.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProvider.java index ec6236432b..570aa9c9c0 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProvider.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProvider.java @@ -19,12 +19,19 @@ package org.apache.guacamole.auth.restrict; +import com.google.inject.Guice; +import com.google.inject.Injector; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.restrict.conf.ConfigurationService; import org.apache.guacamole.auth.restrict.user.RestrictedUserContext; 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.User; import org.apache.guacamole.net.auth.UserContext; +import org.apache.guacamole.net.auth.permission.SystemPermission; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * AuthenticationProvider implementation which provides additional restrictions @@ -33,6 +40,32 @@ */ public class RestrictionAuthenticationProvider extends AbstractAuthenticationProvider { + /** + * Logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(RestrictionAuthenticationProvider.class); + + /** + * Injector which will manage the object graph of this authentication + * provider. + */ + private final Injector injector; + + /** + * Create a new instance of the Restriction authentication provider, + * setting up the Guice injector for dependency management. + * + * @throws GuacamoleException + * If an error occurs configuring the Guice injector. + */ + public RestrictionAuthenticationProvider() throws GuacamoleException { + + // Set up Guice injector. + injector = Guice.createInjector( + new RestrictionAuthenticationProviderModule(this) + ); + } + @Override public String getIdentifier() { return "restrict"; @@ -43,11 +76,38 @@ public UserContext decorate(UserContext context, AuthenticatedUser authenticatedUser, Credentials credentials) throws GuacamoleException { + ConfigurationService confService = injector.getInstance(ConfigurationService.class); + String remoteAddress = credentials.getRemoteAddress(); + User currentUser = context.self(); + boolean isAdmin = currentUser + .getEffectivePermissions() + .getSystemPermissions() + .hasPermission(SystemPermission.Type.ADMINISTER); + boolean restrictAdmins = confService.getRestrictAdminAccounts(); - // Verify identity of user - RestrictionVerificationService.verifyLoginRestrictions(context, - authenticatedUser.getEffectiveUserGroups(), remoteAddress); + // User is admin and restrictions do not apply, log warning. + if (isAdmin && !restrictAdmins) { + LOGGER.warn("Bypassing restrictions for administrator \"{}\"", + currentUser.getIdentifier()); + } + + // User is not an admin, or restrictions do apply to admins + else { + if (isAdmin) { + LOGGER.warn("User \"{}\" is an administrator, but system is " + + "configured to enforce login restrictions on " + + "administrators.", + currentUser.getIdentifier()); + } + else { + LOGGER.debug("User \"{}\" is not an administrator, enforcing" + + "login restrictions.", currentUser.getIdentifier()); + } + RestrictionVerificationService.verifyLoginRestrictions(context, + authenticatedUser.getEffectiveUserGroups(), remoteAddress); + + } // User has been verified, and authentication should be allowed to // continue diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProviderModule.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProviderModule.java new file mode 100644 index 0000000000..c6f5bbd4b7 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionAuthenticationProviderModule.java @@ -0,0 +1,76 @@ +/* + * 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 com.google.inject.AbstractModule; +import org.apache.guacamole.auth.restrict.conf.ConfigurationService; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.environment.LocalEnvironment; +import org.apache.guacamole.net.auth.AuthenticationProvider; + +/** + * Guice module which configures Restriction-specific injections. + */ +public class RestrictionAuthenticationProviderModule extends AbstractModule { + /** + * Guacamole server environment. + */ + private final Environment environment; + + /** + * A reference to the RestrictionAuthenticationProvider on behalf of which + * this module has configured injection. + */ + private final AuthenticationProvider authProvider; + + /** + * Creates a new Restriction authentication provider module which + * configures injection for the RestrictionAuthenticationProvider. + * + * @param authProvider + * The AuthenticationProvider for which injection is being configured. + * + * @throws GuacamoleException + * If an error occurs while retrieving the Guacamole server + * environment. + */ + public RestrictionAuthenticationProviderModule( + AuthenticationProvider authProvider) throws GuacamoleException { + + // Get local environment + this.environment = LocalEnvironment.getInstance(); + + // Store associated auth provider + this.authProvider = authProvider; + + } + + @Override + protected void configure() { + + // Bind core implementations of guacamole-ext classes + bind(AuthenticationProvider.class).toInstance(authProvider); + bind(Environment.class).toInstance(environment); + + // Bind Restriction-specific services + bind(ConfigurationService.class); + + } +} diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionVerificationService.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionVerificationService.java index 41f0e9f295..d17cd53762 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionVerificationService.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/RestrictionVerificationService.java @@ -23,14 +23,14 @@ import inet.ipaddr.HostNameException; import inet.ipaddr.IPAddress; import java.net.UnknownHostException; +import java.text.ParseException; import java.util.Collection; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.restrict.connection.RestrictedConnection; -import org.apache.guacamole.auth.restrict.user.RestrictedUser; -import org.apache.guacamole.auth.restrict.usergroup.RestrictedUserGroup; +import org.apache.guacamole.auth.restrict.form.DateTimeRestrictionField; import org.apache.guacamole.calendar.DailyRestriction; import org.apache.guacamole.calendar.RestrictionType; import org.apache.guacamole.calendar.TimeRestrictionParser; @@ -39,7 +39,6 @@ import org.apache.guacamole.net.auth.User; 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; @@ -54,6 +53,67 @@ public class RestrictionVerificationService { */ private static final Logger LOGGER = LoggerFactory.getLogger(RestrictionVerificationService.class); + /** + * Given the provided strings of an absolute date after which an action is + * valid and before which an action is valid, parse the strings into Date + * objects and determine if the current date and time falls within the + * provided window, returning the appropriate restriction type. + * + * @param afterTimeString + * The string that has the date and time value after which the activity + * is allowed. + * + * @param beforeTimeString + * The string that has the date and time value before which the activity + * is allowed. + * + * @return + * The RestrictionType that represents the allowed or denied state of + * the activity. + */ + private static RestrictionType allowedByDateTimeRestrictions( + String afterTimeString, String beforeTimeString) { + + // Set a default restriction. + RestrictionType dateTimeRestriction = RestrictionType.IMPLICIT_ALLOW; + + // Check the after string and make sure that now is after that date. + if (afterTimeString != null && !afterTimeString.isEmpty()) { + Date now = new Date(); + try { + Date afterTime = DateTimeRestrictionField.parse(afterTimeString); + if (now.before(afterTime)) + return RestrictionType.EXPLICIT_DENY; + } + catch (ParseException e) { + LOGGER.warn("Failed to parse date and time string: {}:", e.getMessage()); + LOGGER.debug("Parse exception while parsing date and time string.", e); + return RestrictionType.IMPLICIT_DENY; + } + dateTimeRestriction = RestrictionType.EXPLICIT_ALLOW; + } + + // Check the before string and make sure that now is prior to that date. + if (beforeTimeString != null && !beforeTimeString.isEmpty()) { + Date now = new Date(); + try { + Date beforeTime = DateTimeRestrictionField.parse(beforeTimeString); + if (now.after(beforeTime)) + return RestrictionType.EXPLICIT_DENY; + } + catch (ParseException e) { + LOGGER.warn("Failed to parse date and time string: {}:", e.getMessage()); + LOGGER.debug("Parse exception while parsing date and time string.", e); + return RestrictionType.IMPLICIT_DENY; + } + dateTimeRestriction = RestrictionType.EXPLICIT_ALLOW; + + } + + // Return the determined RestrictionType for the given date/time strings. + return dateTimeRestriction; + } + /** * Parse out the provided strings of allowed and denied times, verifying * whether or not a login or connection should be allowed at the current @@ -240,19 +300,12 @@ public static void verifyHostRestrictions(UserContext context, // Get the current user User currentUser = context.self(); - // Admins always have access. - if (currentUser.getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.ADMINISTER)) { - LOGGER.warn("User \"{}\" has System Administration permissions; additional restrictions will be bypassed.", - currentUser.getIdentifier()); - return; - } - // Get user's attributes Map userAttributes = currentUser.getAttributes(); // Verify host-based restrictions specific to the user - String allowedHostString = userAttributes.get(RestrictedUser.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); - String deniedHostString = userAttributes.get(RestrictedUser.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); + String allowedHostString = userAttributes.get(Restrictable.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); + String deniedHostString = userAttributes.get(Restrictable.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); RestrictionType hostRestrictionResult = allowedByHostRestrictions(allowedHostString, deniedHostString, remoteAddress); switch (hostRestrictionResult) { @@ -284,8 +337,8 @@ public static void verifyHostRestrictions(UserContext context, Map grpAttributes = userGroup.getAttributes(); // Pull host-based restrictions for this group and verify - String grpAllowedHostString = grpAttributes.get(RestrictedUserGroup.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); - String grpDeniedHostString = grpAttributes.get(RestrictedUserGroup.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); + String grpAllowedHostString = grpAttributes.get(Restrictable.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); + String grpDeniedHostString = grpAttributes.get(Restrictable.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); RestrictionType grpRestrictionResult = allowedByHostRestrictions(grpAllowedHostString, grpDeniedHostString, remoteAddress); // Any explicit denials are thrown immediately @@ -344,8 +397,8 @@ public static void verifyHostRestrictions(Restrictable restrictable, String remoteAddress) throws GuacamoleException { // Verify time-based restrictions specific to this connection. - String allowedHostsString = restrictable.getAttributes().get(RestrictedConnection.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); - String deniedHostsString = restrictable.getAttributes().get(RestrictedConnection.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); + String allowedHostsString = restrictable.getAttributes().get(Restrictable.RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME); + String deniedHostsString = restrictable.getAttributes().get(Restrictable.RESTRICT_HOSTS_DENIED_ATTRIBUTE_NAME); RestrictionType hostRestrictionResult = allowedByHostRestrictions(allowedHostsString, deniedHostsString, remoteAddress); // If the host is not allowed @@ -382,19 +435,12 @@ public static void verifyTimeRestrictions(UserContext context, // Retrieve the current User object associated with the UserContext User currentUser = context.self(); - // Admins always have access. - if (currentUser.getEffectivePermissions().getSystemPermissions().hasPermission(SystemPermission.Type.ADMINISTER)) { - LOGGER.warn("User \"{}\" has System Administration permissions; additional restrictions will be bypassed.", - currentUser.getIdentifier()); - return; - } - // Get user's attributes Map userAttributes = currentUser.getAttributes(); // Verify time-based restrictions specific to the user - String allowedTimeString = userAttributes.get(RestrictedUser.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); - String deniedTimeString = userAttributes.get(RestrictedUser.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); + String allowedTimeString = userAttributes.get(Restrictable.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); + String deniedTimeString = userAttributes.get(Restrictable.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); RestrictionType timeRestrictionResult = allowedByTimeRestrictions(allowedTimeString, deniedTimeString); // Check the time restriction for explicit results. @@ -426,8 +472,8 @@ public static void verifyTimeRestrictions(UserContext context, Map grpAttributes = userGroup.getAttributes(); // Pull time-based restrictions for this group and verify - String grpAllowedTimeString = grpAttributes.get(RestrictedUserGroup.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); - String grpDeniedTimeString = grpAttributes.get(RestrictedUserGroup.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); + String grpAllowedTimeString = grpAttributes.get(Restrictable.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); + String grpDeniedTimeString = grpAttributes.get(Restrictable.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); RestrictionType grpRestrictionResult = allowedByTimeRestrictions(grpAllowedTimeString, grpDeniedTimeString); // An explicit deny results in immediate denial of the login. @@ -463,6 +509,18 @@ public static void verifyTimeRestrictions(UserContext context, } + public static void verifyDateTimeRestrictions(Restrictable restrictable) throws GuacamoleException { + + String afterTimeString = restrictable.getAttributes().get(Restrictable.RESTRICT_TIME_AFTER_ATTRIBUTE_NAME); + String beforeTimeString = restrictable.getAttributes().get(Restrictable.RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME); + RestrictionType dateRestriction = allowedByDateTimeRestrictions(afterTimeString, beforeTimeString); + if (!dateRestriction.isAllowed()) + throw new TranslatableGuacamoleSecurityException( + "Use of this connection or connection group is not allowed at this time.", + "RESTRICT.ERROR_CONNECTION_NOT_ALLOWED_NOW" + ); + } + /** * Verify the time restrictions for the given Connection object, throwing * an exception if the connection should not be allowed, or silently @@ -478,8 +536,8 @@ public static void verifyTimeRestrictions(UserContext context, public static void verifyTimeRestrictions(Restrictable restrictable) throws GuacamoleException { // Verify time-based restrictions specific to this connection. - String allowedTimeString = restrictable.getAttributes().get(RestrictedConnection.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); - String deniedTimeString = restrictable.getAttributes().get(RestrictedConnection.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); + String allowedTimeString = restrictable.getAttributes().get(Restrictable.RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME); + String deniedTimeString = restrictable.getAttributes().get(Restrictable.RESTRICT_TIME_DENIED_ATTRIBUTE_NAME); RestrictionType timeRestriction = allowedByTimeRestrictions(allowedTimeString, deniedTimeString); if (!timeRestriction.isAllowed()) throw new TranslatableGuacamoleSecurityException( @@ -536,6 +594,7 @@ public static void verifyLoginRestrictions(UserContext context, */ public static void verifyConnectionRestrictions(Restrictable restrictable, String remoteAddress) throws GuacamoleException { + verifyDateTimeRestrictions(restrictable); verifyTimeRestrictions(restrictable); verifyHostRestrictions(restrictable, remoteAddress); } diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/ConfigurationService.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/ConfigurationService.java new file mode 100644 index 0000000000..8778da56b2 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/ConfigurationService.java @@ -0,0 +1,58 @@ +/* + * 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.conf; + +import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.environment.Environment; + +/** + * A service for accessing the Guacamaole configuration in order to read + * properties from the guacamole.properties file. + */ +public class ConfigurationService { + + /** + * The environment of the Guacamole Server. + */ + @Inject + private Environment environment; + + /** + * Return true if login restrictions implemented by this extension should + * apply to system admin accounts in addition to non-administrative users, + * or false if administrative accounts should be exempt from those + * restrictions. This will return false by default, exempting system + * administrators from login restrictions imposed by this extension. + * + * @return + * true if login restrictions should apply to Guacamole system + * administrative accounts, otherwise false. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public boolean getRestrictAdminAccounts() throws GuacamoleException { + return environment.getProperty( + RestrictionProperties.RESTRICT_ADMIN_ACCOUNTS, + false + ); + } + +} diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/RestrictionProperties.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/RestrictionProperties.java new file mode 100644 index 0000000000..a3b28eaf24 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/conf/RestrictionProperties.java @@ -0,0 +1,41 @@ +/* + * 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.conf; + +import org.apache.guacamole.properties.BooleanGuacamoleProperty; + +/** + * A class that implements the various properties that this module can + * leverage from guacamole.properties. + */ +public class RestrictionProperties { + + /** + * A property that allows for configuring whether or not Guacamole applies + * the restrictions to admin accounts. By default, login restrictions do + * NOT apply to admin accounts. + */ + public static final BooleanGuacamoleProperty RESTRICT_ADMIN_ACCOUNTS = new BooleanGuacamoleProperty() { + + @Override + public String getName() { return "restrict-admin-accounts"; } + + }; + +} diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connection/RestrictedConnection.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connection/RestrictedConnection.java index bdbce0bcc4..08adee54a6 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connection/RestrictedConnection.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connection/RestrictedConnection.java @@ -26,6 +26,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.restrict.Restrictable; import org.apache.guacamole.auth.restrict.RestrictionVerificationService; +import org.apache.guacamole.auth.restrict.form.DateTimeRestrictionField; import org.apache.guacamole.auth.restrict.form.HostRestrictionField; import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; import org.apache.guacamole.calendar.RestrictionType; @@ -46,49 +47,12 @@ public class RestrictedConnection extends DelegatingConnection implements Restri */ private final String remoteAddress; - /** - * 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 RESTRICT_CONNECTION_ATTRIBUTES = Arrays.asList( + RESTRICT_TIME_AFTER_ATTRIBUTE_NAME, + RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME, RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, @@ -101,6 +65,8 @@ public class RestrictedConnection extends DelegatingConnection implements Restri */ public static final Form RESTRICT_CONNECTION_FORM = new Form("restrict-login-form", Arrays.asList( + new DateTimeRestrictionField(RESTRICT_TIME_AFTER_ATTRIBUTE_NAME), + new DateTimeRestrictionField(RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME), new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connectiongroup/RestrictedConnectionGroup.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connectiongroup/RestrictedConnectionGroup.java index b6c18144ef..1f61a26363 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connectiongroup/RestrictedConnectionGroup.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/connectiongroup/RestrictedConnectionGroup.java @@ -26,6 +26,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.restrict.Restrictable; import org.apache.guacamole.auth.restrict.RestrictionVerificationService; +import org.apache.guacamole.auth.restrict.form.DateTimeRestrictionField; import org.apache.guacamole.auth.restrict.form.HostRestrictionField; import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; import org.apache.guacamole.calendar.RestrictionType; @@ -46,50 +47,13 @@ public class RestrictedConnectionGroup extends DelegatingConnectionGroup impleme */ private final String remoteAddress; - /** - * 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 RESTRICT_CONNECTIONGROUP_ATTRIBUTES = Arrays.asList( + RESTRICT_TIME_AFTER_ATTRIBUTE_NAME, + RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME, RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME, RESTRICT_TIME_DENIED_ATTRIBUTE_NAME, RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME, @@ -102,6 +66,8 @@ public class RestrictedConnectionGroup extends DelegatingConnectionGroup impleme */ public static final Form RESTRICT_CONNECTIONGROUP_FORM = new Form("restrict-login-form", Arrays.asList( + new DateTimeRestrictionField(RESTRICT_TIME_AFTER_ATTRIBUTE_NAME), + new DateTimeRestrictionField(RESTRICT_TIME_BEFORE_ATTRIBUTE_NAME), new TimeRestrictionField(RESTRICT_TIME_ALLOWED_ATTRIBUTE_NAME), new TimeRestrictionField(RESTRICT_TIME_DENIED_ATTRIBUTE_NAME), new HostRestrictionField(RESTRICT_HOSTS_ALLOWED_ATTRIBUTE_NAME), diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/form/DateTimeRestrictionField.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/form/DateTimeRestrictionField.java new file mode 100644 index 0000000000..91ba5724b2 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/form/DateTimeRestrictionField.java @@ -0,0 +1,100 @@ +/* + * 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 java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.apache.guacamole.form.Field; + +/** + * A field that parses a string containing an absolute date and time value. + */ +public class DateTimeRestrictionField extends Field { + + /** + * The field type. + */ + public static final String FIELD_TYPE = "GUAC_DATETIME_RESTRICTION"; + + /** + * The format of the data for this field as it will be stored in the + * underlying storage mechanism. + */ + public static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; + + /** + * 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 DateTimeRestrictionField(String name) { + super(name, FIELD_TYPE); + } + + /** + * Converts the given date into a string which follows the format used by + * date fields. + * + * @param date + * The date value to format, which may be null. + * + * @return + * The formatted date, or null if the provided time was null. + */ + public static String format(Date date) { + DateFormat dateFormat = new SimpleDateFormat(DateTimeRestrictionField.FORMAT); + return date == null ? null : dateFormat.format(date); + } + + /** + * Parses the given string into a corresponding date. The string must + * follow the standard format used by date fields, as defined by FORMAT + * and as would be produced by format(). + * + * @param dateString + * The date string to parse, which may be null. + * + * @return + * The date corresponding to the given date string, or null if the + * provided date string was null or blank. + * + * @throws ParseException + * If the given date string does not conform to the standard format + * used by date fields. + */ + public static Date parse(String dateString) + throws ParseException { + + // Return null if no date provided + if (dateString == null || dateString.isEmpty()) + return null; + + // Parse date according to format + DateFormat dateFormat = new SimpleDateFormat(DateTimeRestrictionField.FORMAT); + return dateFormat.parse(dateString); + + } + +} diff --git a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/usergroup/RestrictedUserGroup.java b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/usergroup/RestrictedUserGroup.java index 2e637872b9..6d2d6c51e0 100644 --- a/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/usergroup/RestrictedUserGroup.java +++ b/extensions/guacamole-auth-restrict/src/main/java/org/apache/guacamole/auth/restrict/usergroup/RestrictedUserGroup.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.guacamole.auth.restrict.Restrictable; import org.apache.guacamole.auth.restrict.form.HostRestrictionField; import org.apache.guacamole.auth.restrict.form.TimeRestrictionField; import org.apache.guacamole.form.Form; @@ -33,48 +34,7 @@ * UserGroup implementation which wraps a UserGroup from another extension and * enforces additional restrictions for members of that group. */ -public class RestrictedUserGroup 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"; +public class RestrictedUserGroup extends DelegatingUserGroup implements Restrictable { /** * The list of all user attributes provided by this UserGroup implementation. diff --git a/extensions/guacamole-auth-restrict/src/main/resources/config/restrictConfig.js b/extensions/guacamole-auth-restrict/src/main/resources/config/restrictConfig.js index 4c63e8a2dd..953b9b9106 100644 --- a/extensions/guacamole-auth-restrict/src/main/resources/config/restrictConfig.js +++ b/extensions/guacamole-auth-restrict/src/main/resources/config/restrictConfig.js @@ -36,5 +36,12 @@ angular.module('guacRestrict').config(['formServiceProvider', controller : 'hostRestrictionFieldController', templateUrl : 'app/ext/restrict/templates/hostRestrictionField.html' }); + + // Define the date and time restriction field + formServiceProvider.registerFieldType('GUAC_DATETIME_RESTRICTION', { + module : 'guacRestrict', + controller : 'dateTimeRestrictionFieldController', + templateUrl : 'app/ext/restrict/templates/dateTimeRestrictionField.html' + }); }]); diff --git a/extensions/guacamole-auth-restrict/src/main/resources/controllers/dateTimeRestrictionFieldController.js b/extensions/guacamole-auth-restrict/src/main/resources/controllers/dateTimeRestrictionFieldController.js new file mode 100644 index 0000000000..6fd2054565 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/resources/controllers/dateTimeRestrictionFieldController.js @@ -0,0 +1,89 @@ +/* + * 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 date+time restriction fields. + */ +angular.module('form').controller('dateTimeRestrictionFieldController', + ['$scope', '$injector', + function dateTimeRestrictionFieldController($scope, $injector) { + + // Required services + var $filter = $injector.get('$filter'); + + /** + * Options which dictate the behavior of the input field model, as defined + * by https://docs.angularjs.org/api/ng/directive/ngModelOptions + * + * @type Object. + */ + $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' + + }; + + /** + * Parses the date and time components of the given string into a Date in + * the UTC timezone. The input string must be in the format + * YYYY-MM-DDTHH:mm:ss (zero-padded). + * + * @param {String} str + * The date+time string to parse. + * + * @returns {Date} + * A Date object, in the UTC timezone, or null if parsing the provided + * string fails. + */ + var parseDate = function parseDate(str) { + + // Parse date, return null if parsing fails + var parsedDate = new Date(str); + if (isNaN(parsedDate.getTime())) + return null; + + return parsedDate; + + }; + + // Update typed value when model is changed + $scope.$watch('model', function modelChanged(model) { + $scope.typedValue = (model ? parseDate(model) : null); + }); + + // Update string value in model when typed value is changed + $scope.$watch('typedValue', function typedValueChanged(typedValue) { + $scope.model = (typedValue ? $filter('date')(typedValue, 'yyyy-MM-ddTHH:mm:ssZ', 'UTC') : ''); + }); + +}]); diff --git a/extensions/guacamole-auth-restrict/src/main/resources/templates/dateTimeRestrictionField.html b/extensions/guacamole-auth-restrict/src/main/resources/templates/dateTimeRestrictionField.html new file mode 100644 index 0000000000..8630761fd7 --- /dev/null +++ b/extensions/guacamole-auth-restrict/src/main/resources/templates/dateTimeRestrictionField.html @@ -0,0 +1,12 @@ +
+ +
\ No newline at end of file diff --git a/extensions/guacamole-auth-restrict/src/main/resources/translations/en.json b/extensions/guacamole-auth-restrict/src/main/resources/translations/en.json index 3eb3d04603..2f52d4097b 100644 --- a/extensions/guacamole-auth-restrict/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-restrict/src/main/resources/translations/en.json @@ -8,7 +8,9 @@ "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_AFTER" : "Date and time after which this connection may be used:", "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED" : "Times connection is allowed to be used:", + "FIELD_HEADER_GUAC_RESTRICT_TIME_BEFORE" : "Date and time before which this connection may be used:", "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED" : "Times connection may not be used:", "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Connection Restrictions" @@ -19,7 +21,9 @@ "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_AFTER" : "Date and time after which this connection group may be used:", "FIELD_HEADER_GUAC_RESTRICT_TIME_ALLOWED" : "Times connection group is allowed to be used:", + "FIELD_HEADER_GUAC_RESTRICT_TIME_BEFORE" : "Date and time before which this connection group may be used:", "FIELD_HEADER_GUAC_RESTRICT_TIME_DENIED" : "Times connection group may not be used:", "SECTION_HEADER_RESTRICT_LOGIN_FORM" : "Additional Connection Restrictions" @@ -35,6 +39,8 @@ "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.", + "FIELD_PLACEHOLDER_DATE_TIME_RESTRICTION" : "YYYY-MM-DD HH:MM:SS", + "TABLE_HEADER_DAY" : "Day", "TABLE_HEADER_END_TIME" : "End Time", "TABLE_HEADER_HOST" : "Host", diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java index d35c07dab1..2b3bcee4b8 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProviderService.java @@ -63,6 +63,28 @@ SSOAuthenticatedUser authenticateUser(Credentials credentials) */ URI getLoginURI() throws GuacamoleException; + /** + * Returns the full URI of the logout endpoint to which a user should be + * redirected when they log out from Guacamole. This allows the user to + * also log out from the SSO identity provider. If no logout endpoint is + * configured, null is returned. + * + * @param idToken + * The ID token or authentication token from the user's session, if + * available. This may be used as an id_token_hint parameter in the + * logout request. May be null if not available. + * + * @return + * The full URI of the SSO logout endpoint, or null if not configured. + * + * @throws GuacamoleException + * If configuration information required for generating the logout URI + * cannot be read. + */ + default URI getLogoutURI(String idToken) throws GuacamoleException { + return null; + } + /** * Frees all resources associated with the relevant * SSOAuthenticationProvider implementation. This function is automatically @@ -70,5 +92,5 @@ SSOAuthenticatedUser authenticateUser(Credentials credentials) * down. */ void shutdown(); - + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java index 91bd39ba2a..f4505006e3 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java @@ -19,9 +19,11 @@ package org.apache.guacamole.auth.sso; import com.google.inject.Inject; +import java.net.URI; import javax.ws.rs.core.Response; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; import org.apache.guacamole.GuacamoleException; /** @@ -55,4 +57,33 @@ public Response redirectToIdentityProvider() throws GuacamoleException { return Response.seeOther(authService.getLoginURI()).build(); } + /** + * Redirects the user to the identity provider's logout endpoint, if + * configured. This allows the user to log out from both Guacamole and the + * SSO identity provider. If no logout endpoint is configured, a 204 No + * Content response is returned. + * + * @param idToken + * The ID token from the user's authentication session, if available. + * This will be included as the id_token_hint parameter in the logout + * request. May be null. + * + * @return + * An HTTP Response that will redirect the user to the IdP logout + * endpoint if configured, or a 204 No Content response otherwise. + * + * @throws GuacamoleException + * If an error occurs preventing the redirect from being created. + */ + @GET + @Path("logout") + public Response getLogoutRedirect( + @QueryParam("id_token") String idToken) throws GuacamoleException { + URI logoutURI = authService.getLogoutURI(idToken); + if (logoutURI != null) + return Response.seeOther(logoutURI).build(); + else + return Response.noContent().build(); + } + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java index ca1be9e1f6..dc559c6b72 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java @@ -130,9 +130,33 @@ public URI getLoginURI() throws GuacamoleException { .build(); } + @Override + public URI getLogoutURI(String idToken) throws GuacamoleException { + + // If no logout endpoint is configured, return null + URI logoutEndpoint = confService.getLogoutEndpoint(); + if (logoutEndpoint == null) + return null; + + // Build the logout URI with appropriate parameters + UriBuilder logoutUriBuilder = UriBuilder.fromUri(logoutEndpoint); + + // Add post_logout_redirect_uri parameter + logoutUriBuilder.queryParam("post_logout_redirect_uri", + confService.getPostLogoutRedirectURI()); + + // Add id_token_hint if available, otherwise add client_id + if (idToken != null && !idToken.isEmpty()) + logoutUriBuilder.queryParam("id_token_hint", idToken); + else + logoutUriBuilder.queryParam("client_id", confService.getClientID()); + + return logoutUriBuilder.build(); + } + @Override public void shutdown() { // Nothing to clean up } - + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java index 96c6426f51..29aee31396 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java @@ -217,7 +217,32 @@ public class ConfigurationService { @Override public String getName() { return "openid-redirect-uri"; } - + + }; + + /** + * The logout endpoint (URI) of the OpenID service. If specified, users + * will be redirected to this endpoint when they log out, allowing them + * to log out from the OpenID provider as well. + */ + private static final URIGuacamoleProperty OPENID_LOGOUT_ENDPOINT = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "openid-logout-endpoint"; } + + }; + + /** + * The URI that the OpenID service should redirect to after logout is + * complete. If not specified, the main redirect URI will be used. + */ + private static final URIGuacamoleProperty OPENID_POST_LOGOUT_REDIRECT_URI = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "openid-post-logout-redirect-uri"; } + }; /** @@ -429,4 +454,36 @@ public int getMaxNonceValidity() throws GuacamoleException { return environment.getProperty(OPENID_MAX_NONCE_VALIDITY, DEFAULT_MAX_NONCE_VALIDITY); } + /** + * Returns the logout endpoint (URI) of the OpenID service, as configured + * with guacamole.properties. If configured, users will be redirected to + * this endpoint when they log out from Guacamole. + * + * @return + * The logout endpoint of the OpenID service, as configured with + * guacamole.properties, or null if not configured. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getLogoutEndpoint() throws GuacamoleException { + return environment.getProperty(OPENID_LOGOUT_ENDPOINT); + } + + /** + * Returns the URI that the OpenID service should redirect to after logout + * is complete, as configured with guacamole.properties. If not configured, + * the main redirect URI will be used. + * + * @return + * The post-logout redirect URI, as configured with + * guacamole.properties, or the main redirect URI if not specified. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getPostLogoutRedirectURI() throws GuacamoleException { + return environment.getProperty(OPENID_POST_LOGOUT_REDIRECT_URI, getRedirectURI()); + } + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js new file mode 100644 index 0000000000..61db138877 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/openidModule.js @@ -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. + */ + +/** + * The module for code implementing SSO using OpenID Connect. + */ +angular.module('guacSsoOpenid', []); + +/** + * Redirect to the SSO logout endpoint when the user logs out, allowing + * Single Logout to occur if configured. + */ +angular.module('guacSsoOpenid').run(['$rootScope', '$window', + function guacSsoOpenidLogout($rootScope, $window) { + + // Redirect to SSO logout endpoint when user logs out + $rootScope.$on('guacLogout', function handleLogout() { + $window.location.href = 'api/ext/openid/logout'; + }); + +}]); + +// Ensure the guacSsoOpenid module is loaded along with the rest of the app +angular.module('index').requires.push('guacSsoOpenid'); diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/pom.xml b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/pom.xml index 67515beab8..f2d5a9e0c5 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/pom.xml +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/pom.xml @@ -36,6 +36,52 @@ ../../ + + + + + + com.github.buckelieg + minify-maven-plugin + + + default-cli + + UTF-8 + + ${basedir}/src/main/resources + ${project.build.directory}/classes + + / + / + saml.js + + + license.txt + + + + **/*.js + + CLOSURE + + + + OFF + OFF + + + + + minify + + + + + + + + diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java index ace19d55ea..f026916d43 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java @@ -24,9 +24,11 @@ import com.google.inject.Singleton; import java.net.URI; import java.util.Arrays; +import javax.ws.rs.core.UriBuilder; import org.apache.guacamole.auth.saml.user.SAMLAuthenticatedUser; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.saml.acs.AssertedIdentity; +import org.apache.guacamole.auth.saml.conf.ConfigurationService; import org.apache.guacamole.auth.saml.acs.SAMLAuthenticationSessionManager; import org.apache.guacamole.auth.saml.acs.SAMLService; import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService; @@ -68,6 +70,12 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS @Inject private SAMLService saml; + /** + * Service for retrieving SAML configuration information. + */ + @Inject + private ConfigurationService confService; + /** * Return the value of the session identifier associated with the given * credentials, or null if no session identifier is found in the @@ -117,9 +125,25 @@ public URI getLoginURI() throws GuacamoleException { return saml.createRequest(); } + @Override + public URI getLogoutURI(String idToken) throws GuacamoleException { + + // If no logout endpoint is configured, return null + URI logoutEndpoint = confService.getLogoutEndpoint(); + if (logoutEndpoint == null) + return null; + + // Build the logout URI with post-logout redirect + UriBuilder logoutUriBuilder = UriBuilder.fromUri(logoutEndpoint); + logoutUriBuilder.queryParam("RelayState", + confService.getPostLogoutRedirectURI()); + + return logoutUriBuilder.build(); + } + @Override public void shutdown() { sessionManager.shutdown(); } - + } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java index 47ead88208..da10043eee 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/conf/ConfigurationService.java @@ -190,6 +190,30 @@ public class ConfigurationService { }; + /** + * The logout endpoint (URI) of the SAML IdP. When configured, users will + * be redirected to this endpoint when they log out of Guacamole. + */ + private static final URIGuacamoleProperty SAML_LOGOUT_ENDPOINT = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "saml-logout-endpoint"; } + + }; + + /** + * The URI to redirect to after logout is complete. Defaults to the + * callback URL if not specified. + */ + private static final URIGuacamoleProperty SAML_POST_LOGOUT_REDIRECT_URI = + new URIGuacamoleProperty() { + + @Override + public String getName() { return "saml-post-logout-redirect-uri"; } + + }; + /** * The Guacamole server environment. */ @@ -390,6 +414,37 @@ public File getPrivateKeyFile() throws GuacamoleException { return environment.getProperty(SAML_PRIVATE_KEY_PATH); } + /** + * Returns the logout endpoint (URI) of the SAML IdP. If not configured, + * null will be returned and logout will only end the Guacamole session. + * + * @return + * The logout endpoint of the SAML IdP, or null if not configured. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getLogoutEndpoint() throws GuacamoleException { + return environment.getProperty(SAML_LOGOUT_ENDPOINT); + } + + /** + * Returns the URI to redirect to after logout is complete. If not + * configured, the callback URL will be used. + * + * @return + * The post-logout redirect URI. + * + * @throws GuacamoleException + * If guacamole.properties cannot be parsed. + */ + public URI getPostLogoutRedirectURI() throws GuacamoleException { + URI postLogoutUri = environment.getProperty(SAML_POST_LOGOUT_REDIRECT_URI); + if (postLogoutUri != null) + return postLogoutUri; + return getCallbackUrl(); + } + /** * Returns the contents of a small file, such as a private key or certificate into * a String. If the file does not exist, or cannot be read for any reason, an exception diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resource-templates/guac-manifest.json b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resource-templates/guac-manifest.json index f19c11593f..64db565cbc 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resource-templates/guac-manifest.json +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resource-templates/guac-manifest.json @@ -17,6 +17,10 @@ "styles/sso-providers.css" ], + "js" : [ + "saml.min.js" + ], + "html" : [ "html/sso-providers.html", "html/sso-provider-saml.html" diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/samlModule.js b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/samlModule.js new file mode 100644 index 0000000000..b3ee9869e6 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/samlModule.js @@ -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. + */ + +/** + * The module for code implementing SSO using SAML. + */ +angular.module('guacSsoSaml', []); + +/** + * Redirect to the SSO logout endpoint when the user logs out, allowing + * Single Logout to occur if configured. + */ +angular.module('guacSsoSaml').run(['$rootScope', '$window', + function guacSsoSamlLogout($rootScope, $window) { + + // Redirect to SSO logout endpoint when user logs out + $rootScope.$on('guacLogout', function handleLogout() { + $window.location.href = 'api/ext/saml/logout'; + }); + +}]); + +// Ensure the guacSsoSaml module is loaded along with the rest of the app +angular.module('index').requires.push('guacSsoSaml'); diff --git a/extensions/guacamole-auth-totp/src/main/resources/translations/tr.json b/extensions/guacamole-auth-totp/src/main/resources/translations/tr.json index 43363b1ccd..c6f4e0d997 100644 --- a/extensions/guacamole-auth-totp/src/main/resources/translations/tr.json +++ b/extensions/guacamole-auth-totp/src/main/resources/translations/tr.json @@ -13,6 +13,7 @@ "FIELD_HEADER_DIGITS" : "Rakamlar:", "FIELD_HEADER_INTERVAL" : "Aralık:", "FIELD_HEADER_SECRET_KEY" : "Gizli Anahtar:", + "FIELD_PLACEHOLDER_CODE" : "Kimlik Doğrulama Kodu", "HELP_ENROLL_BARCODE" : "Kayıt işlemini tamamlamak için aşağıdaki kare kodu cihazınızdaki veya telefonunuzdaki iki faktörlü kimlik doğrulama uygulamasında tarayın.", diff --git a/guacamole-common-js/src/main/webapp/modules/ClipboardEventInterpreter.js b/guacamole-common-js/src/main/webapp/modules/ClipboardEventInterpreter.js new file mode 100644 index 0000000000..2376413593 --- /dev/null +++ b/guacamole-common-js/src/main/webapp/modules/ClipboardEventInterpreter.js @@ -0,0 +1,195 @@ +/* + * 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. + */ + +var Guacamole = Guacamole || {}; + +/** + * An interpreter for clipboard events within a Guacamole session recording. + * Clipboard data arrives as a sequence of instructions: clipboard (declares + * stream and mimetype), blob (contains base64 data), and end (terminates stream). + * + * @constructor + * @param {number} [startTimestamp=0] + * The starting timestamp for the recording. Event timestamps will be + * relative to this value. + */ +Guacamole.ClipboardEventInterpreter = function ClipboardEventInterpreter(startTimestamp) { + + if (startTimestamp === undefined || startTimestamp === null) + startTimestamp = 0; + + /** + * All clipboard events parsed so far. + * + * @private + * @type {!Guacamole.ClipboardEventInterpreter.ClipboardEvent[]} + */ + var parsedEvents = []; + + /** + * Map of active clipboard streams, keyed by stream index. + * Each entry tracks the mimetype and accumulated base64 data. + * + * @private + * @type {Object.} + */ + var activeStreams = {}; + + /** + * The timestamp of the most recent instruction, used for events + * that don't have their own timestamp. + * + * @private + * @type {number} + */ + var lastTimestamp = 0; + + /** + * Updates the last known timestamp. + * + * @param {number} timestamp + * The absolute timestamp from a sync instruction or key event. + */ + this.setTimestamp = function setTimestamp(timestamp) { + lastTimestamp = timestamp; + }; + + /** + * Handles a clipboard instruction, which begins a new clipboard stream. + * + * @param {!string[]} args + * The arguments: [stream_index, mimetype] + */ + this.handleClipboard = function handleClipboard(args) { + var streamIndex = args[0]; + var mimetype = args[1]; + + activeStreams[streamIndex] = { + mimetype: mimetype, + data: '', + timestamp: lastTimestamp + }; + }; + + /** + * Handles a blob instruction, which contains base64-encoded data + * for an active stream. + * + * @param {!string[]} args + * The arguments: [stream_index, base64_data] + */ + this.handleBlob = function handleBlob(args) { + var streamIndex = args[0]; + var base64Data = args[1]; + + var stream = activeStreams[streamIndex]; + if (stream) + stream.data += base64Data; + }; + + /** + * Handles an end instruction, which completes a clipboard stream + * and creates the final clipboard event. + * + * @param {!string[]} args + * The arguments: [stream_index] + */ + this.handleEnd = function handleEnd(args) { + var streamIndex = args[0]; + + var stream = activeStreams[streamIndex]; + if (stream) { + // Decode the base64 data + var decodedData = ''; + try { + decodedData = atob(stream.data); + // Handle UTF-8 decoding + decodedData = decodeURIComponent(escape(decodedData)); + } catch (e) { + // If decoding fails, use raw decoded data or mark as binary + try { + decodedData = atob(stream.data); + } catch (e2) { + decodedData = '[Binary data]'; + } + } + + // Create the clipboard event. A clipboard stream may arrive + // before the first sync instruction has set lastTimestamp, in + // which case stream.timestamp - startTimestamp is negative. + // Clamp to 0 so pre-connection clipboard syncs land at the + // start of the playback timeline rather than producing a + // negative timestamp that breaks playerTimeService.formatTime(). + parsedEvents.push(new Guacamole.ClipboardEventInterpreter.ClipboardEvent({ + mimetype: stream.mimetype, + data: decodedData, + timestamp: Math.max(0, stream.timestamp - startTimestamp) + })); + + // Clean up the stream + delete activeStreams[streamIndex]; + } + }; + + /** + * Returns all parsed clipboard events. + * + * @returns {Guacamole.ClipboardEventInterpreter.ClipboardEvent[]} + * All clipboard events parsed so far. + */ + this.getEvents = function getEvents() { + return parsedEvents; + }; + +}; + +/** + * A single clipboard event from a recording. + * + * @constructor + * @param {Guacamole.ClipboardEventInterpreter.ClipboardEvent|object} [template={}] + * The object whose properties should be copied. + */ +Guacamole.ClipboardEventInterpreter.ClipboardEvent = function ClipboardEvent(template) { + + template = template || {}; + + /** + * The mimetype of the clipboard data. + * + * @type {!string} + */ + this.mimetype = template.mimetype || 'text/plain'; + + /** + * The clipboard content (decoded from base64). + * + * @type {!string} + */ + this.data = template.data || ''; + + /** + * The timestamp when this clipboard event occurred, relative to + * the start of the recording. + * + * @type {!number} + */ + this.timestamp = template.timestamp || 0; + +}; \ No newline at end of file diff --git a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js index 819854b85c..1dc2e9acd2 100644 --- a/guacamole-common-js/src/main/webapp/modules/SessionRecording.js +++ b/guacamole-common-js/src/main/webapp/modules/SessionRecording.js @@ -428,16 +428,24 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) var keyEventInterpreter = null; /** - * Initialize the key interpreter. This function should be called only once - * with the first timestamp in the recording as an argument. + * A clipboard event interpreter to extract all clipboard events from + * this recording. + * + * @type {Guacamole.ClipboardEventInterpreter} + */ + var clipboardEventInterpreter = null; + + /** + * Initialize the key and clipboard interpreters. This function should be + * called only once with the first timestamp in the recording. * * @private * @param {!number} startTimestamp - * The timestamp of the first frame in the recording, i.e. the start of - * the recording. + * The timestamp of the first frame in the recording. */ - function initializeKeyInterpreter(startTimestamp) { + function initializeInterpreters(startTimestamp) { keyEventInterpreter = new Guacamole.KeyEventInterpreter(startTimestamp); + clipboardEventInterpreter = new Guacamole.ClipboardEventInterpreter(startTimestamp); } /** @@ -466,6 +474,12 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) // Parse frame timestamp from sync instruction var timestamp = parseInt(args[0]); + // Update the clipboard interpreter's timestamp so clipboard events + // are recorded with the correct time. Clipboard events don't have + // their own timestamps + if (clipboardEventInterpreter) + clipboardEventInterpreter.setTimestamp(timestamp); + // Add a new frame containing the instructions read since last frame var frame = new Guacamole.SessionRecording._Frame(timestamp, frameStart, frameEnd); frames.push(frame); @@ -474,7 +488,7 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) // If this is the first frame, intialize the key event interpreter // with the timestamp of the first frame if (frames.length === 1) - initializeKeyInterpreter(timestamp); + initializeInterpreters(timestamp); // This frame should eventually become a keyframe if enough data // has been processed and enough recording time has elapsed, or if @@ -492,8 +506,27 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) } - else if (opcode === 'key') + else if (opcode === 'key') { keyEventInterpreter.handleKeyEvent(args); + // The clipboard gets updated on Ctrl+C key events so we update the + // clipboard interpreter's timestamp to match the timestamp as + // clipboard events don't have own timestamps + if (clipboardEventInterpreter) { + var keyTimestamp = parseInt(args[2]); + clipboardEventInterpreter.setTimestamp(keyTimestamp); + } + } + + else if (opcode === 'clipboard' && clipboardEventInterpreter) + clipboardEventInterpreter.handleClipboard(args); + + // Handle blob data (may be clipboard data) + else if (opcode === 'blob' && clipboardEventInterpreter) + clipboardEventInterpreter.handleBlob(args); + + // Handle stream end (may complete clipboard stream) + else if (opcode === 'end' && clipboardEventInterpreter) + clipboardEventInterpreter.handleEnd(args); }; /** @@ -562,9 +595,14 @@ Guacamole.SessionRecording = function SessionRecording(source, refreshInterval) // Now that the recording is fully processed, and all key events // have been extracted, call the onkeyevents handler if defined - if (recording.onkeyevents) + if (recording.onkeyevents && keyEventInterpreter) recording.onkeyevents(keyEventInterpreter.getEvents()); + // call the onclipboardevents handler if defined with extracted + // clipboard events + if (recording.onclipboardevents && clipboardEventInterpreter) + recording.onclipboardevents(clipboardEventInterpreter.getEvents()); + // Consider recording loaded if tunnel has closed without errors if (!errorEncountered) notifyLoaded(); diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java b/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java index a9766face3..673e134d90 100644 --- a/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java +++ b/guacamole-ext/src/main/java/org/apache/guacamole/token/TokenFilter.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Filtering object which replaces tokens of the form "${TOKEN_NAME}" with @@ -33,6 +35,11 @@ */ public class TokenFilter { + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class); + /** * Regular expression which matches individual tokens, with additional * capturing groups for convenient retrieval of leading text, the possible @@ -225,6 +232,17 @@ private String filter(String input, boolean strict) // strict mode is enabled if (tokenValue == null) { + // Token marked as optional, so just skip it and update + // last match. + if (modifier != null && modifier.equals("OPTIONAL")) { + LOGGER.debug("The token \"{}\" has no value and has been " + + "marked as optional, so it will be treated " + + "as a blank value instead of a literal.", + tokenName); + endOfLastMatch = tokenMatcher.end(); + continue; + } + // Fail outright if strict mode is enabled if (strict) throw new GuacamoleTokenUndefinedException("Token " @@ -232,8 +250,7 @@ private String filter(String input, boolean strict) // If strict mode is NOT enabled, simply interpret as // a literal - String notToken = tokenMatcher.group(TOKEN_GROUP); - output.append(notToken); + output.append(tokenMatcher.group(TOKEN_GROUP)); } diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json index 300d2ee3c3..cb99e0dfd7 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/kubernetes.json @@ -99,6 +99,11 @@ { "name" : "clipboard", "fields" : [ + { + "name" : "clipboard-buffer-size", + "type" : "ENUM", + "options" : [ "", "262144", "1048576", "10485760" ] + }, { "name" : "disable-copy", "type" : "BOOLEAN", @@ -119,6 +124,11 @@ "name" : "backspace", "type" : "ENUM", "options" : [ "", "127", "8" ] + }, + { + "name" : "func-keys-and-keypad", + "type" : "ENUM", + "options" : [ "", "esc", "vt100" ] } ] }, @@ -173,6 +183,11 @@ "type" : "BOOLEAN", "options" : [ "true" ] }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, { "name" : "create-recording-path", "type" : "BOOLEAN", diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json index c59be16335..ad12241e06 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json @@ -192,6 +192,11 @@ "type" : "ENUM", "options" : [ "", "preserve", "unix", "windows" ] }, + { + "name" : "clipboard-buffer-size", + "type" : "ENUM", + "options" : [ "", "262144", "1048576", "10485760", "52428800" ] + }, { "name" : "disable-copy", "type" : "BOOLEAN", @@ -396,6 +401,11 @@ "type" : "BOOLEAN", "options" : [ "true" ] }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, { "name" : "create-recording-path", "type" : "BOOLEAN", diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json index 91d1fef098..77c1bdfb5d 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json @@ -82,6 +82,11 @@ { "name" : "clipboard", "fields" : [ + { + "name" : "clipboard-buffer-size", + "type" : "ENUM", + "options" : [ "", "262144", "1048576", "10485760" ] + }, { "name" : "disable-copy", "type" : "BOOLEAN", @@ -125,6 +130,11 @@ "type" : "ENUM", "options" : [ "", "127", "8" ] }, + { + "name" : "func-keys-and-keypad", + "type" : "ENUM", + "options" : [ "", "esc", "vt100" ] + }, { "name" : "terminal-type", "type" : "ENUM", @@ -183,6 +193,11 @@ "type" : "BOOLEAN", "options" : [ "true" ] }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, { "name" : "create-recording-path", "type" : "BOOLEAN", diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json index 041c3acc98..0d534d072e 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/telnet.json @@ -82,6 +82,11 @@ { "name" : "clipboard", "fields" : [ + { + "name" : "clipboard-buffer-size", + "type" : "ENUM", + "options" : [ "", "262144", "1048576", "10485760" ] + }, { "name" : "disable-copy", "type" : "BOOLEAN", @@ -103,6 +108,11 @@ "type" : "ENUM", "options" : [ "", "127", "8" ] }, + { + "name" : "func-keys-and-keypad", + "type" : "ENUM", + "options" : [ "", "esc", "vt100" ] + }, { "name" : "terminal-type", "type" : "ENUM", @@ -161,6 +171,11 @@ "type" : "BOOLEAN", "options" : [ "true" ] }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, { "name" : "create-recording-path", "type" : "BOOLEAN", diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/vnc.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/vnc.json index 44242a24a3..a6587c4c24 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/vnc.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/vnc.json @@ -88,6 +88,11 @@ { "name" : "clipboard", "fields" : [ + { + "name" : "clipboard-buffer-size", + "type" : "ENUM", + "options" : [ "", "262144", "1048576", "10485760", "52428800" ] + }, { "name" : "clipboard-encoding", "type" : "ENUM", @@ -146,6 +151,11 @@ "type" : "BOOLEAN", "options" : [ "true" ] }, + { + "name" : "recording-include-clipboard", + "type" : "BOOLEAN", + "options" : [ "true" ] + }, { "name" : "create-recording-path", "type" : "BOOLEAN", diff --git a/guacamole/src/main/frontend/src/app/client/services/guacClientManager.js b/guacamole/src/main/frontend/src/app/client/services/guacClientManager.js index 990d10cdec..bcbb7a3f08 100644 --- a/guacamole/src/main/frontend/src/app/client/services/guacClientManager.js +++ b/guacamole/src/main/frontend/src/app/client/services/guacClientManager.js @@ -320,8 +320,27 @@ angular.module('client').factory('guacClientManager', ['$injector', }; + /** + * Handles the beforeunload event. If any managed clients are active, the + * user is prompted to confirm leaving the page. + * + * @param {event} e + * The beforeunload event. + */ + service.beforeUnload = function beforeUnload(e) { + const managedClients = storedManagedClients(); + + if (Object.keys(managedClients).length > 0) { + e.preventDefault(); + + // For older browsers + e.returnValue = true; + } + }; + // Disconnect all clients when window is unloaded $window.addEventListener('unload', service.clear); + $window.addEventListener('beforeunload', service.beforeUnload); return service; diff --git a/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js b/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js index 01769ea94f..b7e83a3a05 100644 --- a/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js +++ b/guacamole/src/main/frontend/src/app/client/types/ManagedClient.js @@ -62,12 +62,49 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ var THUMBNAIL_UPDATE_FREQUENCY = 5000; + /** + * A deferred pipe stream, that has yet to be consumed, as well as all + * axuilary information needed to pull data from the stream. + * + * @constructor + * @param {DeferredPipeStream|Object} [template={}] + * The object whose properties should be copied within the new + * DeferredPipeStream. + */ + var DeferredPipeStream = function DeferredPipeStream(template) { + + // Use empty object by default + template = template || {}; + + /** + * The stream that will receive data from the server. + * + * @type Guacamole.InputStream + */ + this.stream = template.stream; + + /** + * The mimetype of the data which will be received. + * + * @type String + */ + this.mimetype = template.mimetype; + + /** + * The name of the pipe. + * + * @type String + */ + this.name = template.name; + + }; + /** * Object which serves as a surrogate interface, encapsulating a Guacamole * client while it is active, allowing it to be maintained in the * background. One or more ManagedClients are grouped within * ManagedClientGroups before being attached to the client view. - * + * * @constructor * @param {ManagedClient|Object} [template={}] * The object whose properties should be copied within the new @@ -240,6 +277,22 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', */ this.arguments = template.arguments || {}; + /** + * Any received pipe streams that have not been consumed by an onpipe + * handler or registered pipe handler, indexed by pipe stream name. + * + * @type {Object.} + */ + this.deferredPipeStreams = template.deferredPipeStreams || {}; + + /** + * Handlers for deferred pipe streams, indexed by the name of the pipe + * stream that the handler should handle. + * + * @type {Object.} + */ + this.deferredPipeStreamHandlers = template.deferredPipeStreamHandlers || {}; + }; /** @@ -553,6 +606,25 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + // A default onpipe implementation that will automatically defer any + // received pipe streams, automatically invoking any registered handlers + // that may already be set for the received name + client.onpipe = (stream, mimetype, name) => { + + // Defer the pipe stream + managedClient.deferredPipeStreams[name] = new DeferredPipeStream( + { stream, mimetype, name }); + + // Invoke the handler now, if set + const handler = managedClient.deferredPipeStreamHandlers[name]; + if (handler) { + + // Handle the stream, and clear from the deferred streams + handler(stream, mimetype, name); + delete managedClient.deferredPipeStreams[name]; + } + }; + // Test for argument mutability whenever an argument value is // received client.onargv = function clientArgumentValueReceived(stream, mimetype, name) { @@ -1004,6 +1076,72 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', }; + + /** + * Register a handler that will be automatically invoked for any deferred + * pipe stream with the provided name, either when a pipe stream with a + * name matching a registered handler is received, or immediately when this + * function is called, if such a pipe stream has already been received. + * + * NOTE: Pipe streams are automatically deferred by the default onpipe + * implementation. To preserve this behavior when using a custom onpipe + * callback, make sure to defer to the default implementation as needed. + * + * @param {ManagedClient} managedClient + * The client for which the deferred pipe stream handler should be set. + * + * @param {String} name + * The name of the pipe stream that should be handeled by the provided + * handler. If another handler is already registered for this name, it + * will be replaced by the handler provided to this function. + * + * @param {Function} handler + * The handler that should handle any deferred pipe stream with the + * provided name. This function must take the same arguments as the + * standard onpipe handler - namely, the stream itself, the mimetype, + * and the name. + */ + ManagedClient.registerDeferredPipeHandler = function registerDeferredPipeHandler( + managedClient, name, handler) { + managedClient.deferredPipeStreamHandlers[name] = handler; + + // Invoke the handler now, if the pipestream has already been received + if (managedClient.deferredPipeStreams[name]) { + + // Invoke the handler with the deferred pipe stream + var deferredStream = managedClient.deferredPipeStreams[name]; + handler(deferredStream.stream, + deferredStream.mimetype, + deferredStream.name); + + // Clean up the now-consumed pipe stream + delete managedClient.deferredPipeStreams[name]; + } + }; + + /** + * Detach the provided deferred pipe stream handler, if it is currently + * registered for the provided pipe stream name. + * + * @param {String} name + * The name of the associated pipe stream for the handler that should + * be detached. + * + * @param {Function} handler + * The handler that should be detached. + * + * @param {ManagedClient} managedClient + * The client for which the deferred pipe stream handler should be + * detached. + */ + ManagedClient.detachDeferredPipeHandler = function detachDeferredPipeHandler( + managedClient, name, handler) { + + // Remove the handler if found + if (managedClient.deferredPipeStreamHandlers[name] === handler) + delete managedClient.deferredPipeStreamHandlers[name]; + }; + return ManagedClient; }]); diff --git a/guacamole/src/main/frontend/src/app/player/directives/player.js b/guacamole/src/main/frontend/src/app/player/directives/player.js index 8b4adb4efe..fe2a0228b9 100644 --- a/guacamole/src/main/frontend/src/app/player/directives/player.js +++ b/guacamole/src/main/frontend/src/app/player/directives/player.js @@ -291,6 +291,22 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay */ var keyTimestamps = []; + /** + * Clipboard events extracted from the recording, stored for merging + * with key events. + * + * @type {!Guacamole.ClipboardEventInterpreter.ClipboardEvent[]} + */ + var clipboardEvents = []; + + /** + * Key events extracted from the recording, stored for merging + * with clipboard events. + * + * @type {!Guacamole.KeyEventInterpreter.KeyEvent[]} + */ + var keyEvents = []; + /** * Return true if any batches of key event logs are available for this * recording, or false otherwise. @@ -495,11 +511,25 @@ angular.module('player').directive('guacPlayer', ['$injector', function guacPlay // Extract key events from the recording $scope.recording.onkeyevents = function keyEventsReceived(events) { + keyEvents = events; + keyTimestamps = events.map(event => event.timestamp); + // Convert to a display-optimized format - $scope.textBatches = ( - keyEventDisplayService.parseEvents(events)); + $scope.textBatches = keyEventDisplayService.parseEventsWithClipboard( + keyEvents, clipboardEvents + ); - keyTimestamps = events.map(event => event.timestamp); + }; + + // Extract clipboard events from the recording + $scope.recording.onclipboardevents = function clipboardEventsReceived(events) { + + clipboardEvents = events; + + // Convert to a display-optimized format + $scope.textBatches = keyEventDisplayService.parseEventsWithClipboard( + keyEvents, clipboardEvents + ); }; diff --git a/guacamole/src/main/frontend/src/app/player/services/keyEventDisplayService.js b/guacamole/src/main/frontend/src/app/player/services/keyEventDisplayService.js index c362670a76..0a9ae6b439 100644 --- a/guacamole/src/main/frontend/src/app/player/services/keyEventDisplayService.js +++ b/guacamole/src/main/frontend/src/app/player/services/keyEventDisplayService.js @@ -190,32 +190,47 @@ angular.module('player').factory('keyEventDisplayService', }; /** - * Accepts key events in the format produced by KeyEventInterpreter and returns - * human readable text batches, seperated by at least `batchSeperation` milliseconds - * if provided. + * Accepts key events and clipboard events, merging them chronologically + * into human readable text batches, separated by at least `batchSeparation` + * milliseconds if provided. * * NOTE: The event processing logic and output format is based on the `guaclog` - * tool, with the addition of batching support. + * tool, with the addition of batching and clipboard support. * - * @param {Guacamole.KeyEventInterpreter.KeyEvent[]} [rawEvents] + * @param {Guacamole.KeyEventInterpreter.KeyEvent[]} [rawKeyEvents] * The raw key events to prepare for display. * - * @param {number} [batchSeperation=5000] + * @param {Guacamole.ClipboardEventInterpreter.ClipboardEvent[]} [clipboardEvents] + * The clipboard events to merge inline with key events. + * + * @param {number} [batchSeparation=5000] * The minimum number of milliseconds that must elapse between subsequent * batches of key-event-generated text. If 0 or negative, no splitting will - * occur, resulting in a single batch for all provided key events. + * occur, resulting in a single batch for all provided events. * * @param {boolean} [consolidateEvents=false] * Whether consecutive sequences of events with similar properties * should be consolidated into a single ConsolidatedKeyEvent object for * display performance reasons. */ - service.parseEvents = function parseEvents( - rawEvents, batchSeperation, consolidateEvents) { + service.parseEventsWithClipboard = function parseEventsWithClipboard( + rawKeyEvents, clipboardEvents, batchSeparation, consolidateEvents) { + + // Default to 5 seconds if the batch separation was not provided + if (batchSeparation === undefined || batchSeparation === null) + batchSeparation = 5000; + + // Convert clipboard events to unified format + const clipboardAsEvents = (clipboardEvents || []).map(event => ({ + isClipboard: true, + timestamp: event.timestamp, + data: event.data + })); + + // Merge and sort all events by timestamp + const allEvents = [...(rawKeyEvents || []), ...clipboardAsEvents] + .sort((a, b) => a.timestamp - b.timestamp); - // Default to 5 seconds if the batch seperation was not provided - if (batchSeperation === undefined || batchSeperation === null) - batchSeperation = 5000; /** * A map of X11 keysyms to a KeyDefinition object, if the corresponding * key is currently pressed. If a keysym has no entry in this map at all @@ -225,24 +240,22 @@ angular.module('player').factory('keyEventDisplayService', */ const pressedKeys = {}; - // The timestamp of the most recent key event processed - let lastKeyEvent = 0; + // The timestamp of the most recent event processed + let lastEventTime = 0; - // All text batches produced from the provided raw key events + // All text batches produced from the provided events const batches = [new service.TextBatch()]; - // Process every provided raw - _.forEach(rawEvents, event => { + // Process every provided event + allEvents.forEach(event => { - // Extract all fields from the raw event - const { definition, pressed, timestamp } = event; - const { keysym, name, value } = definition; + const { timestamp } = event; // Only switch to a new batch of text if sufficient time has passed - // since the last key event - const newBatch = (batchSeperation >= 0 - && (timestamp - lastKeyEvent) >= batchSeperation); - lastKeyEvent = timestamp; + // since the last event + const newBatch = (batchSeparation >= 0 + && (timestamp - lastEventTime) >= batchSeparation); + lastEventTime = timestamp; if (newBatch) batches.push(new service.TextBatch()); @@ -250,7 +263,7 @@ angular.module('player').factory('keyEventDisplayService', const currentBatch = _.last(batches); /** - * Either push the a new event constructed using the provided fields + * Either push a new event constructed using the provided fields * into the latest batch, or consolidate into the latest event as * appropriate given the consolidation configuration and event type. * @@ -274,94 +287,109 @@ angular.module('player').factory('keyEventDisplayService', // Otherwise, push a new event else { currentBatch.events.push(new service.ConsolidatedKeyEvent({ - text, typed, timestamp})); + text, typed, timestamp + })); currentBatch.simpleValue += text; } - } + }; - // Track modifier state - if (MODIFIER_KEYS[keysym]) { - if (pressed) - pressedKeys[keysym] = definition; - else - delete pressedKeys[keysym]; + // Handle clipboard events + if (event.isClipboard) { + const preview = (event.data || '').substring(0, 50); + pushEvent('[Clipboard: ' + preview + ']', false); } - // Append to the current typed value when a printable - // (non-modifier) key is pressed - else if (pressed) { + // Handle key events + else { - // If any shorcut keys are currently pressed - if (_.some(pressedKeys, (def, key) => SHORTCUT_KEYS[key])) { + const { definition, pressed } = event; + const { keysym, name, value } = definition; - var shortcutText = '<'; + // Track modifier state + if (MODIFIER_KEYS[keysym]) { + if (pressed) + pressedKeys[keysym] = definition; + else + delete pressedKeys[keysym]; + } - var firstKey = true; + // Append to the current typed value when a printable + // (non-modifier) key is pressed + else if (pressed) { - // Compose entry by inspecting the state of each tracked key. - // At least one key must be pressed when in a shortcut. - for (let pressedKeysym in pressedKeys) { + // If any shortcut keys are currently pressed + if (_.some(pressedKeys, (def, key) => SHORTCUT_KEYS[key])) { - var pressedKeyDefinition = pressedKeys[pressedKeysym]; + var shortcutText = '<'; - // Print name of key - if (firstKey) { - shortcutText += pressedKeyDefinition.name; - firstKey = false; - } + var firstKey = true; - else - shortcutText += ('+' + pressedKeyDefinition.name); + // Compose entry by inspecting the state of each tracked key. + // At least one key must be pressed when in a shortcut. + for (let pressedKeysym in pressedKeys) { - } + var pressedKeyDefinition = pressedKeys[pressedKeysym]; - // Finally, append the printable key to close the shortcut - shortcutText += ('+' + name + '>') + // Print name of key + if (firstKey) { + shortcutText += pressedKeyDefinition.name; + firstKey = false; + } - // Add the shortcut to the current batch - pushEvent(shortcutText, false); - } + else + shortcutText += ('+' + pressedKeyDefinition.name); - // Print the key itself - else { + } + + // Finally, append the printable key to close the shortcut + shortcutText += ('+' + name + '>'); - var keyText; - var typed; - - // Print the value if explicitly defined - if (value !== undefined) { - - keyText = value; - typed = true; - - // If the name should be printed in addition, add it as a - // seperate event before the actual character value - if (PRINT_NAME_TOO_KEYS[keysym]) - pushEvent(formatKeyName(name), false); - + // Add the shortcut to the current batch + pushEvent(shortcutText, false); } - - // Otherwise print the name + + // Print the key itself else { - - keyText = formatKeyName(name); - - // While this is a representation for a single character, - // the key text is the name of the key, not the actual - // character itself - typed = false; - - } - - // Add the key to the current batch - pushEvent(keyText, typed); + var keyText; + var typed; + + // Print the value if explicitly defined + if (value !== undefined) { + + keyText = value; + typed = true; + + // If the name should be printed in addition, add it as a + // separate event before the actual character value + if (PRINT_NAME_TOO_KEYS[keysym]) + pushEvent(formatKeyName(name), false); + + } + + // Otherwise print the name + else { + + keyText = formatKeyName(name); + + // While this is a representation for a single character, + // the key text is the name of the key, not the actual + // character itself + typed = false; + + } + + // Add the key to the current batch + pushEvent(keyText, typed); + + } } - } - // We ignore key release events here because in practice characters - // are printed when you press keys not release them. The release order - // can be different and lead to wrong character printing order in the log. + // We ignore key release events here because in practice characters + // are printed when you press keys not release them. The release order + // can be different and lead to wrong character printing order in the log. + + } }); @@ -372,4 +400,4 @@ angular.module('player').factory('keyEventDisplayService', return service; -}]); +}]); \ No newline at end of file diff --git a/guacamole/src/main/frontend/src/images/logo-192.png b/guacamole/src/main/frontend/src/images/logo-192.png new file mode 100644 index 0000000000..d5e7f6b928 Binary files /dev/null and b/guacamole/src/main/frontend/src/images/logo-192.png differ diff --git a/guacamole/src/main/frontend/src/images/logo-512.png b/guacamole/src/main/frontend/src/images/logo-512.png new file mode 100644 index 0000000000..5fcd201c2f Binary files /dev/null and b/guacamole/src/main/frontend/src/images/logo-512.png differ diff --git a/guacamole/src/main/frontend/src/images/logo-vector.svg b/guacamole/src/main/frontend/src/images/logo-vector.svg new file mode 100644 index 0000000000..2ec0cf1bcc --- /dev/null +++ b/guacamole/src/main/frontend/src/images/logo-vector.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/guacamole/src/main/frontend/src/index.html b/guacamole/src/main/frontend/src/index.html index e332c2a605..f59b7cf018 100644 --- a/guacamole/src/main/frontend/src/index.html +++ b/guacamole/src/main/frontend/src/index.html @@ -28,6 +28,7 @@ + <% for (var index in htmlWebpackPlugin.files.css) { %> diff --git a/guacamole/src/main/frontend/src/manifest.json b/guacamole/src/main/frontend/src/manifest.json new file mode 100644 index 0000000000..8ec765339e --- /dev/null +++ b/guacamole/src/main/frontend/src/manifest.json @@ -0,0 +1,24 @@ +{ + "name": "Guacamole", + "short_name": "Guacamole", + "description": "Guacamole", + "start_url": ".", + "display": "standalone", + "icons": [ + { + "src": "./images/logo-vector.svg", + "type": "image/svg+xml", + "sizes": "512x512" + }, + { + "src": "./images/logo-512.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "./images/logo-192.png", + "type": "image/png", + "sizes": "192x192" + } + ] +} diff --git a/guacamole/src/main/frontend/src/translations/en.json b/guacamole/src/main/frontend/src/translations/en.json index d81e18afc2..6d44118ebb 100644 --- a/guacamole/src/main/frontend/src/translations/en.json +++ b/guacamole/src/main/frontend/src/translations/en.json @@ -509,6 +509,7 @@ "FIELD_HEADER_CA_CERT" : "Certificate authority certificate:", "FIELD_HEADER_CLIENT_CERT" : "Client certificate:", "FIELD_HEADER_CLIENT_KEY" : "Client key:", + "FIELD_HEADER_CLIPBOARD_BUFFER_SIZE" : "Clipboard data size limit:", "FIELD_HEADER_COLOR_SCHEME" : "Color scheme:", "FIELD_HEADER_CONTAINER" : "Container name:", "FIELD_HEADER_CREATE_RECORDING_PATH" : "Automatically create recording path:", @@ -518,6 +519,7 @@ "FIELD_HEADER_EXEC_COMMAND" : "Command (exec):", "FIELD_HEADER_FONT_NAME" : "Font name:", "FIELD_HEADER_FONT_SIZE" : "Font size:", + "FIELD_HEADER_FUNC_KEYS_AND_KEYPAD" : "Function keys and keypad behavior:", "FIELD_HEADER_HOSTNAME" : "Hostname:", "FIELD_HEADER_IGNORE_CERT" : "Ignore server certificate:", "FIELD_HEADER_NAMESPACE" : "Namespace:", @@ -528,6 +530,7 @@ "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Exclude mouse:", "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:", "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD" : "Include clipboard events:", "FIELD_HEADER_RECORDING_NAME" : "Recording name:", "FIELD_HEADER_RECORDING_PATH" : "Recording path:", "FIELD_HEADER_SCROLLBACK" : "Maximum scrollback size:", @@ -540,6 +543,11 @@ "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", "FIELD_OPTION_BACKSPACE_127" : "Delete (Ctrl-?)", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_262144" : "256KB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_1048576" : "1MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_10485760" : "10MB", + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Black on white", "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Gray on black", @@ -562,6 +570,10 @@ "FIELD_OPTION_FONT_SIZE_96" : "96", "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_ESC" : "ESC[n~", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_VT100" : "VT100", + "NAME" : "Kubernetes", "SECTION_HEADER_AUTHENTICATION" : "Authentication", @@ -580,6 +592,7 @@ "FIELD_HEADER_CERT_TOFU" : "Trust host certificate on first use:", "FIELD_HEADER_CERT_FINGERPRINTS" : "Fingerprints of trusted host certificates:", "FIELD_HEADER_CLIENT_NAME" : "Client name:", + "FIELD_HEADER_CLIPBOARD_BUFFER_SIZE" : "Clipboard data size limit:", "FIELD_HEADER_COLOR_DEPTH" : "Color depth:", "FIELD_HEADER_CONSOLE" : "Administrator console:", "FIELD_HEADER_CONSOLE_AUDIO" : "Support audio in console:", @@ -633,6 +646,7 @@ "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:", "FIELD_HEADER_RECORDING_EXCLUDE_TOUCH" : "Exclude touch events:", "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD" : "Include clipboard events:", "FIELD_HEADER_RECORDING_NAME" : "Recording name:", "FIELD_HEADER_RECORDING_PATH" : "Recording path:", "FIELD_HEADER_RESIZE_METHOD" : "Resize method:", @@ -666,6 +680,12 @@ "FIELD_HEADER_WOL_UDP_PORT" : "UDP port for WoL packet: ", "FIELD_HEADER_WOL_WAIT_TIME" : "Host boot wait time:", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_262144" : "256KB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_1048576" : "1MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_10485760" : "10MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_52428800" : "50MB", + "FIELD_OPTION_NORMALIZE_CLIPBOARD_EMPTY" : "", "FIELD_OPTION_NORMALIZE_CLIPBOARD_PRESERVE" : "Preserve as-is", "FIELD_OPTION_NORMALIZE_CLIPBOARD_UNIX" : "Linux/Mac/Unix (LF)", @@ -735,6 +755,7 @@ "PROTOCOL_SSH" : { "FIELD_HEADER_BACKSPACE" : "Backspace key sends:", + "FIELD_HEADER_CLIPBOARD_BUFFER_SIZE" : "Clipboard data size limit:", "FIELD_HEADER_COLOR_SCHEME" : "Color scheme:", "FIELD_HEADER_COMMAND" : "Execute command:", "FIELD_HEADER_CREATE_RECORDING_PATH" : "Automatically create recording path:", @@ -743,6 +764,7 @@ "FIELD_HEADER_DISABLE_PASTE" : "Disable pasting from client:", "FIELD_HEADER_FONT_NAME" : "Font name:", "FIELD_HEADER_FONT_SIZE" : "Font size:", + "FIELD_HEADER_FUNC_KEYS_AND_KEYPAD" : "Function keys and keypad behavior:", "FIELD_HEADER_ENABLE_SFTP" : "Enable SFTP:", "FIELD_HEADER_HOST_KEY" : "Public host key (Base64):", "FIELD_HEADER_HOSTNAME" : "Hostname:", @@ -759,6 +781,7 @@ "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Exclude mouse:", "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:", "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD" : "Include clipboard events:", "FIELD_HEADER_RECORDING_NAME" : "Recording name:", "FIELD_HEADER_RECORDING_PATH" : "Recording path:", "FIELD_HEADER_SERVER_ALIVE_INTERVAL" : "Server keepalive interval:", @@ -781,6 +804,11 @@ "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", "FIELD_OPTION_BACKSPACE_127" : "Delete (Ctrl-?)", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_262144" : "256KB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_1048576" : "1MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_10485760" : "10MB", + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Black on white", "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Gray on black", @@ -803,6 +831,10 @@ "FIELD_OPTION_FONT_SIZE_96" : "96", "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_ESC" : "ESC[n~", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_VT100" : "VT100", + "FIELD_OPTION_TERMINAL_TYPE_ANSI" : "ansi", "FIELD_OPTION_TERMINAL_TYPE_EMPTY" : "", "FIELD_OPTION_TERMINAL_TYPE_LINUX" : "linux", @@ -829,6 +861,7 @@ "PROTOCOL_TELNET" : { "FIELD_HEADER_BACKSPACE" : "Backspace key sends:", + "FIELD_HEADER_CLIPBOARD_BUFFER_SIZE" : "Clipboard data size limit:", "FIELD_HEADER_COLOR_SCHEME" : "Color scheme:", "FIELD_HEADER_CREATE_RECORDING_PATH" : "Automatically create recording path:", "FIELD_HEADER_CREATE_TYPESCRIPT_PATH" : "Automatically create typescript path:", @@ -836,6 +869,7 @@ "FIELD_HEADER_DISABLE_PASTE" : "Disable pasting from client:", "FIELD_HEADER_FONT_NAME" : "Font name:", "FIELD_HEADER_FONT_SIZE" : "Font size:", + "FIELD_HEADER_FUNC_KEYS_AND_KEYPAD" : "Function keys and keypad behavior:", "FIELD_HEADER_HOSTNAME" : "Hostname:", "FIELD_HEADER_LOGIN_FAILURE_REGEX" : "Login failure regular expression:", "FIELD_HEADER_LOGIN_SUCCESS_REGEX" : "Login success regular expression:", @@ -849,6 +883,7 @@ "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Exclude mouse:", "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:", "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD" : "Include clipboard events:", "FIELD_HEADER_RECORDING_NAME" : "Recording name:", "FIELD_HEADER_RECORDING_PATH" : "Recording path:", "FIELD_HEADER_SCROLLBACK" : "Maximum scrollback size:", @@ -867,6 +902,11 @@ "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", "FIELD_OPTION_BACKSPACE_127" : "Delete (Ctrl-?)", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_262144" : "256KB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_1048576" : "1MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_10485760" : "10MB", + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Black on white", "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Gray on black", @@ -889,6 +929,10 @@ "FIELD_OPTION_FONT_SIZE_96" : "96", "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_EMPTY" : "", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_ESC" : "ESC[n~", + "FIELD_OPTION_FUNC_KEYS_AND_KEYPAD_VT100" : "VT100", + "FIELD_OPTION_TERMINAL_TYPE_ANSI" : "ansi", "FIELD_OPTION_TERMINAL_TYPE_EMPTY" : "", "FIELD_OPTION_TERMINAL_TYPE_LINUX" : "linux", @@ -913,6 +957,7 @@ "PROTOCOL_VNC" : { "FIELD_HEADER_AUDIO_SERVERNAME" : "Audio server name:", + "FIELD_HEADER_CLIPBOARD_BUFFER_SIZE" : "Clipboard data size limit:", "FIELD_HEADER_CLIPBOARD_ENCODING" : "Encoding:", "FIELD_HEADER_COLOR_DEPTH" : "Color depth:", "FIELD_HEADER_COMPRESS_LEVEL" : "Compression level:", @@ -938,6 +983,7 @@ "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Exclude mouse:", "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Exclude graphics/streams:", "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Include key events:", + "FIELD_HEADER_RECORDING_INCLUDE_CLIPBOARD" : "Include clipboard events:", "FIELD_HEADER_RECORDING_NAME" : "Recording name:", "FIELD_HEADER_RECORDING_PATH" : "Recording path:", "FIELD_HEADER_SFTP_DIRECTORY" : "Default upload directory:", @@ -961,6 +1007,12 @@ "FIELD_HEADER_WOL_UDP_PORT" : "UDP port for WoL packet:", "FIELD_HEADER_WOL_WAIT_TIME" : "Host boot wait time:", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_262144" : "256KB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_1048576" : "1MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_10485760" : "10MB", + "FIELD_OPTION_CLIPBOARD_BUFFER_SIZE_52428800" : "50MB", + "FIELD_OPTION_COLOR_DEPTH_8" : "256 color", "FIELD_OPTION_COLOR_DEPTH_16" : "Low color (16-bit)", "FIELD_OPTION_COLOR_DEPTH_24" : "True color (24-bit)", diff --git a/guacamole/src/main/frontend/src/translations/it.json b/guacamole/src/main/frontend/src/translations/it.json index db146a5955..aab724c190 100644 --- a/guacamole/src/main/frontend/src/translations/it.json +++ b/guacamole/src/main/frontend/src/translations/it.json @@ -6,48 +6,73 @@ "ACTION_ACKNOWLEDGE" : "OK", "ACTION_CANCEL" : "Annulla", + "ACTION_CLEAR" : "Pulisci", "ACTION_CLONE" : "Clona", "ACTION_CONTINUE" : "Continua", "ACTION_DELETE" : "Cancella", "ACTION_DELETE_SESSIONS" : "Termina Sessione", + "ACTION_DOWNLOAD" : "Scarica", + "ACTION_IMPORT" : "Importa", "ACTION_LOGIN" : "Entra", + "ACTION_LOGIN_AGAIN" : "Rientra", "ACTION_LOGOUT" : "Esci", "ACTION_MANAGE_CONNECTIONS" : "Connessioni", "ACTION_MANAGE_PREFERENCES" : "Preferenze", "ACTION_MANAGE_SETTINGS" : "Opzioni", "ACTION_MANAGE_SESSIONS" : "Sessioni Attive", "ACTION_MANAGE_USERS" : "Utenti", + "ACTION_MANAGE_USER_GROUPS" : "Gruppi", "ACTION_NAVIGATE_BACK" : "Indietro", "ACTION_NAVIGATE_HOME" : "Home", + "ACTION_PAUSE" : "Pausa", + "ACTION_PLAY" : "Avvia", "ACTION_SAVE" : "Salva", + "ACTION_SEARCH" : "Ricerca", + "ACTION_SHARE" : "Condividere", "ACTION_UPDATE_PASSWORD" : "Aggiorna Password", + "ACTION_VIEW_HISTORY" : "Storico", + "ACTION_VIEW_RECORDING" : "Registrazione", - "DIALOG_HEADER_ERROR" : "Errore", + "DIALOG_HEADER_ERROR" : "Errore", + "ERROR_PAGE_UNAVAILABLE" : "Si è verificato un errore e l'azione non è stata completata. Se il problema persiste, contattare l'amministratore di sistema o controllare i registri di sistema.", "ERROR_PASSWORD_BLANK" : "La password non può essere vuota.", "ERROR_PASSWORD_MISMATCH" : "Le password inserite sono diverse!", + "ERROR_SINGLE_FILE_ONLY" : "Si prega di caricare solo un file alla volta", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PASSWORD_AGAIN" : "Re-inserisci la password:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PASSWORD_AGAIN" : "Re-inserisci la password:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "Consenti la scrittura sul file di registrazione esistente:", + "FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING" : "Consenti la scrittura sul file TypeScript esistente:", "FIELD_PLACEHOLDER_FILTER" : "Filtro", "FORMAT_DATE_TIME_PRECISE" : "dd-MM-yyyy HH:mm:ss", - "INFO_ACTIVE_USER_COUNT" : "Ora utilizzato da {USERS} {USERS, plural, one{user} other{users}}." + "INFO_ACTIVE_USER_COUNT" : "Ora utilizzato da {USERS} {USERS, plural, one{utente} other{utenti}}.", + "INFO_LOGGED_OUT" : "Sei stato disconnesso.", + + "TEXT_ANONYMOUS_USER" : "Anonimo", + "TEXT_HISTORY_DURATION" : "{VALUE} {UNIT, select, second{{VALUE, plural, one{secondo} other{secondi}}} minute{{VALUE, plural, one{minuto} other{minuti}}} hour{{VALUE, plural, one{ora} other{ore}}} day{{VALUE, plural, one{giorno} other{giorni}}} other{}}" }, "CLIENT" : { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLEAR_CLIENT_MESSAGES" : "@:APP.ACTION_CLEAR", "ACTION_CLEAR_COMPLETED_TRANSFERS" : "Pulisci i trasferimenti completati", + "ACTION_CONTINUE" : "@:APP.ACTION_CONTINUE", "ACTION_DISCONNECT" : "Disconnetti", + "ACTION_FULLSCREEN" : "Schermo intero", "ACTION_LOGOUT" : "@:APP.ACTION_LOGOUT", "ACTION_NAVIGATE_BACK" : "@:APP.ACTION_NAVIGATE_BACK", "ACTION_NAVIGATE_HOME" : "@:APP.ACTION_NAVIGATE_HOME", "ACTION_RECONNECT" : "Riconnetti", "ACTION_SAVE_FILE" : "@:APP.ACTION_SAVE", + "ACTION_SHARE" : "@:APP.ACTION_SHARE", + "ACTION_SHOW_CLIPBOARD" : "Fare clic per visualizzare il contenuto degli appunti.", "ACTION_UPLOAD_FILES" : "Carica un file", "DIALOG_HEADER_CONNECTING" : "Connessione in corso", @@ -57,7 +82,11 @@ "ERROR_CLIENT_201" : "La connessione è stata chiusa perchè il server è sovraccarico. Attendi qualche minuto e riprova a collegarti.", "ERROR_CLIENT_202" : "Il server Guacamole ha chiuso la connessione perchè il desktop remoto risponde troppo lentamente. Per favore riprova o contatta il tuo amministratore di sistema.", "ERROR_CLIENT_203" : "Il desktop remoto ha riscontrato un errore ed ha chiuso la connessione. Per favore riprova o contatta il tuo amministratore di sistema.", - "ERROR_CLIENT_205" : "Questa connessione è stata chiusa a causa di un conflitto con un'altra connessione. Riprova tra qualche minuto.", + "ERROR_CLIENT_207" : "Il server desktop remoto è al momento irraggiungibile. Se il problema persiste, informa l'amministratore di sistema o controlla i registri di sistema.", + "ERROR_CLIENT_208" : "Il server desktop remoto non è al momento disponibile. Se il problema persiste, contatta l'amministratore di sistema o controlla i registri di sistema.", + "ERROR_CLIENT_209" : "Il server desktop remoto ha chiuso la connessione perché è in conflitto con un'altra connessione. Riprova più tardi.", + "ERROR_CLIENT_20A" : "Il server desktop remoto ha chiuso la connessione perché sembrava inattivo. Se ciò non è desiderato o è inaspettato, informa l'amministratore di sistema o controlla le impostazioni di sistema.", + "ERROR_CLIENT_20B" : "Il server desktop remoto ha chiuso forzatamente la connessione. Se ciò non è desiderato o è inaspettato, informa l'amministratore di sistema o controlla i registri di sistema.", "ERROR_CLIENT_301" : "Autenticazione fallita. Prova ad effettaure nuovamente la connessione.", "ERROR_CLIENT_303" : "Non hai i permessi per accedere a questa connessione. Se ne hai bisogno, chiedi ai tuoi amministratori di sistema di aggiungerti agli utenti abilitati.", "ERROR_CLIENT_308" : "Il server Guacamole ha chiuso la connessione perchè il tuo browser non ha risposto per troppo tempo e sembrava essersi disconnesso. Solitamente la causa è un problema di rete, ad esempio un segnale wifi instabile o una connesisone molto lenta. Controlla la tua rete e riprova.", @@ -69,6 +98,8 @@ "ERROR_TUNNEL_203" : "Si è verificato un errore sul server e la connessione è stata chiusa. Riprova o contatta il tuo amministratore di sistema.", "ERROR_TUNNEL_204" : "La connessione richiesta non esiste. Controlla il nome della connessione e riprova. Grazie.", "ERROR_TUNNEL_205" : "Questa connessione è già in uso, non sono possibili accessi concorrenti. Riprova più tardi. Grazie.", + "ERROR_TUNNEL_207" : "Il server Guacamole non è al momento raggiungibile. Controlla la tua rete e riprova.", + "ERROR_TUNNEL_208" : "Il server Guacamole non accetta connessioni. Controlla la tua rete e riprova.", "ERROR_TUNNEL_301" : "Non hai i permessi per accedere a questa connessione perchè non hai effettuato il login. Inserisci nome utente e password e riprova.", "ERROR_TUNNEL_303" : "Non hai i permessi per accedere a questa connessione. Se ne hai bisogno, chiedi ai tuoi amministratori di sistema di aggiungerti agli utenti abilitati.", "ERROR_TUNNEL_308" : "Il server Guacamole ha chiuso la connessione perchè il tuo browser non ha risposto per troppo tempo e sembrava essersi disconnesso. Solitamente la causa è un problema di rete, ad esempio un segnale wifi instabile o una connesisone molto lenta. Controlla la tua rete e riprova.", @@ -96,8 +127,10 @@ "HELP_MOUSE_MODE" : "Determina come si deve comportare il mouse remoto in base al touch.", "HELP_MOUSE_MODE_ABSOLUTE" : "Tap to click. Il click è sostituito dal tocco.", "HELP_MOUSE_MODE_RELATIVE" : "Trascina il dito per muovere il puntatore del mouse e fai tap al posto del click. Il click sarà effettuato dove si trova il puntatore.", + "HELP_SHARE_LINK" : "La connessione attuale è condivisa e può essere utilizzata da chiunque disponga dei seguenti {LINKS, plurale, uno{link} altri{links}}:", - "INFO_NO_FILE_TRANSFERS" : "Nessun trasferimento di file.", + "INFO_CONNECTION_SHARED" : "Questa connessione è ora condivisa.", + "INFO_NO_FILE_TRANSFERS" : "Nessun trasferimento di file.", "NAME_INPUT_METHOD_NONE" : "Nessuno", "NAME_INPUT_METHOD_OSK" : "Tastiera su schermo", @@ -109,27 +142,129 @@ "NAME_MOUSE_MODE_ABSOLUTE" : "Touchscreen", "NAME_MOUSE_MODE_RELATIVE" : "Touchpad", - "SECTION_HEADER_CLIPBOARD" : "Appunti", - "SECTION_HEADER_DEVICES" : "Dispositivi", - "SECTION_HEADER_DISPLAY" : "Schermo", - "SECTION_HEADER_FILE_TRANSFERS" : "Trasferimento file", - "SECTION_HEADER_INPUT_METHOD" : "Metodo di input", - "SECTION_HEADER_MOUSE_MODE" : "Modalità di emulazione del mouse", + "SECTION_HEADER_CLIENT_MESSAGES" : "Messaggi", + "SECTION_HEADER_CLIPBOARD" : "Appunti", + "SECTION_HEADER_DEVICES" : "Dispositivi", + "SECTION_HEADER_DISPLAY" : "Schermo", + "SECTION_HEADER_FILE_TRANSFERS" : "Trasferimento file", + "SECTION_HEADER_INPUT_METHOD" : "Metodo di input", + + "SECTION_HEADER_MOUSE_MODE" : "Modalità di emulazione del mouse", + "TEXT_ANONYMOUS_USER_JOINED" : "Un utente anonimo si è unito alla connessione.", + "TEXT_ANONYMOUS_USER_LEFT" : "Un utente anonimo ha abbandonato la connessione.", "TEXT_ZOOM_AUTO_FIT" : "Adatta automaticamente alla finestra del browser", "TEXT_CLIENT_STATUS_IDLE" : "Inattivo.", "TEXT_CLIENT_STATUS_CONNECTING" : "Connessione in corso a Guacamole...", "TEXT_CLIENT_STATUS_DISCONNECTED" : "Sei stato disconnesso.", "TEXT_CLIENT_STATUS_WAITING" : "Connesso a Guacamole. Attendi una risposta...", - "TEXT_RECONNECT_COUNTDOWN" : "Riconnessione in {REMAINING} {REMAINING, plural, one{second} other{seconds}}...", + "TEXT_USER_JOINED" : "{USERNAME} si è unito alla connessione.", + "TEXT_USER_LEFT" : "{USERNAME} ha lasciato la connessione.", + "TEXT_RECONNECT_COUNTDOWN" : "Riconnessione in {REMAINING} {REMAINING, plural, one{secondo} other{secondi}}...", "TEXT_FILE_TRANSFER_PROGRESS" : "{PROGRESS} {UNIT, select, b{B} kb{KB} mb{MB} gb{GB} other{}}", - "URL_OSK_LAYOUT" : "layouts/it-it-qwerty.json" + "URL_OSK_LAYOUT" : "layouts/it-it-qwerty.json" + + }, + + "COLOR_SCHEME" : { + + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_HIDE_DETAILS" : "Nascondi", + "ACTION_SAVE" : "@:APP.ACTION_SAVE", + "ACTION_SHOW_DETAILS" : "Mostra", + + "FIELD_HEADER_BACKGROUND" : "Sfondo", + "FIELD_HEADER_FOREGROUND" : "Primo piano", + + "FIELD_OPTION_CUSTOM" : "Presonalizzato...", + + "SECTION_HEADER_DETAILS" : "Dettagli:" + + }, + "IMPORT": { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_BROWSE" : "Cerca file", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLEAR" : "@:APP.ACTION_CLEAR", + "ACTION_VIEW_FORMAT_HELP" : "Visualizza suggerimenti sul formato", + "ACTION_IMPORT" : "@:APP.ACTION_IMPORT", + "ACTION_IMPORT_CONNECTIONS" : "Importa Connessioni", + + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_SUCCESS" : "Successo", + + "ERROR_AMBIGUOUS_CSV_HEADER" : "Intestazione CSV ambigua \"{HEADER}\" potrebbe essere un attributo di connessione o un parametro", + "ERROR_AMBIGUOUS_PARENT_GROUP" : "Sia group che parentIdentifier non possono essere specificati contemporaneamente", + "ERROR_ARRAY_REQUIRED" : "Il file fornito deve contenere un elenco di connessioni", + "ERROR_DETECTED_INVALID_TYPE" : "Tipo di file non supportato. Assicurati che il file sia un CSV, JSON o YAML valido.", + "ERROR_DUPLICATE_CONNECTION_IN_FILE" : "Connessione duplicata \"{NAME}\" in \"{PATH}\" nel file di importazione", + "ERROR_DUPLICATE_CSV_HEADER" : "Intestazione CSV duplicata: {HEADER}", + "ERROR_EMPTY_FILE" : "Il file fornito è vuoto", + "ERROR_INVALID_CSV_HEADER" : "Intestazione CSV non valida \"{HEADER}\" non è né un attributo né un parametro", + "ERROR_INVALID_MIME_TYPE" : "Tipo di file non supportato: \"{TYPE}\"", + "ERROR_INVALID_GROUP" : "Nessun gruppo corrispondente a \"{GROUP}\" trovato", + "ERROR_INVALID_GROUP_IDENTIFIER" : "Nessun gruppo di connessione con identificatore \"{IDENTIFIER}\" trovato", + "ERROR_INVALID_GROUP_TYPE" : "Gruppo non valido - deve essere una stringa.", + "ERROR_INVALID_PROTOCOL" : "Protocollo non valido \"{PROTOCOL}\"", + "ERROR_INVALID_USER_GROUPS_TYPE" : "Gruppi utenti non validi - devono essere un array di identificatori di gruppi utenti.", + "ERROR_INVALID_USERS_TYPE" : "Utenti non validi - devono essere un array di identificatori utente.", + "ERROR_NO_FILE_SUPPLIED" : "Seleziona un file da importare", + "ERROR_PARSE_FAILURE_CSV" : "Assicurati che il file sia un CSV valido. Analisi non riuscita con errore \"{ERROR}\".", + "ERROR_PARSE_FAILURE_JSON" : "Assicurati che il file sia un JSON valido. Analisi non riuscita con errore \"{ERROR}\".", + "ERROR_PARSE_FAILURE_YAML" : "Assicurati che il file sia in formato YAML valido. Analisi non riuscita con errore \"{ERROR}\".", + "ERROR_REJECT_UPDATE_CONNECTION" : "La connessione \"{NAME}\" esiste già in \"{PATH}\"", + "ERROR_REQUIRED_NAME_CONNECTION" : "Il nome della connessione è obbligatorio", + "ERROR_REQUIRED_PROTOCOL_CONNECTION" : "Il protocollo di connessione è obbligatorio", + "ERROR_REQUIRED_NAME_FILE" : "Nessun nome di connessione trovato nel file fornito", + "ERROR_REQUIRED_PROTOCOL_FILE" : "Nessun protocollo di connessione trovato nel file fornito", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + + "FIELD_HEADER_EXISTING_CONNECTION_MODE" : "Replace/Update existing connections", + "FIELD_HEADER_EXISTING_PERMISSION_MODE" : "Reset permissions", + + "HELP_CSV_DESCRIPTION" : "Un file CSV di importazione delle connessioni contiene un record di connessione per riga. Ogni colonna specifica un campo di connessione. È necessario specificare almeno il nome e il protocollo della connessione.", + "HELP_CSV_EXAMPLE" : "nome,protocollo,nome utente,password,nome host,gruppo,utenti,gruppi,guacd-encryption (attributo)\nconn1,vnc,alice,pass1,conn1.web.com,ROOT,utente guac 1;utente guac 2,Utenti della connessione 1,nessuno\nconn2,rdp,bob,pass2,conn2.web.com,ROOT/Gruppo padre,utente guac 1,,ssl\nconn3,ssh,carol,pass3,conn3.web.com,ROOT/Gruppo padre/Gruppo figlio,utente guac 2;utente guac 3,,\nconn4,kubernetes,,,,,,,", + "HELP_CSV_MORE_DETAILS" : "L'intestazione CSV di ogni riga specifica il campo di connessione. L'ID del gruppo di connessione in cui importare la connessione può essere specificato direttamente con \"parentIdentifier\", oppure il percorso del gruppo padre può essere specificato utilizzando \"group\", come mostrato di seguito. Nella maggior parte dei casi, non dovrebbero esserci conflitti tra i campi, ma se necessario, è possibile aggiungere un suffisso \"(attributo)\" o \"(parametro)\" per evitare ambiguità. Gli elenchi di identificatori di utenti o gruppi di utenti devono essere separati da punto e virgola..¹", + "HELP_FILE_TYPE_DESCRIPTION" : "Sono supportati tre tipi di file per l'importazione delle connessioni: CSV, JSON e YAML. Gli stessi dati possono essere specificati per ciascun tipo di file. Questo deve includere il nome e il protocollo della connessione. Facoltativamente, è possibile specificare anche la posizione del gruppo di connessione, un elenco di utenti e/o gruppi di utenti a cui concedere l'accesso, parametri di connessione o protocolli di connessione. Tutti gli utenti o gruppi di utenti non presenti nell'origine dati corrente verranno creati automaticamente. Si noti che le autorizzazioni di connessione esistenti non verranno rimosse per le connessioni aggiornate, a meno che non sia selezionata l'opzione \"Reimposta autorizzazioni\"..", + "HELP_FILE_TYPE_HEADER" : "Tipi di File", + "HELP_JSON_DESCRIPTION" : "Un file JSON di importazione delle connessioni è un elenco di oggetti di connessione. In ogni oggetto di connessione devono essere specificati almeno il nome e il protocollo della connessione.", + "HELP_JSON_EXAMPLE" : "[\n \\{\n \"nome\": \"conn1\",\n \"protocollo\": \"vnc\",\n \"parametri\": \\{ \"nomeutente\": \"alice\", \"password\": \"pass1\", \"nomehost\": \"conn1.web.com\" \\},\n \"parentIdentifier\": \"ROOT\",\n \"utenti\": [ \"utente guac 1\", \"utente guac 2\" ],\n \"gruppi\": [ \"Utenti Connessione 1\" ],\n \"attributi\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"nome\": \"conn2\",\n \"protocollo\": \"rdp\",\n \"parametri\": \\{ \"nomeutente\": \"bob\", \"password\": \"pass2\", \"hostname\": \"conn2.web.com\" \\},\n \"group\": \"ROOT/Gruppo padre\",\n \"users\": [ \"guac user 1\" ],\n \"attributes\": \\{ \"guacd-encryption\": \"none\" \\}\n \\},\n \\{\n \"name\": \"conn3\",\n \"protocol\": \"ssh\",\n \"parameters\": \\{ \"username\": \"carol\", \"password\": \"pass3\", \"hostname\": \"conn3.web.com\" \\},\n \"group\": \"ROOT/Gruppo padre/Gruppo figlio\",\n \"users\": [ \"guac user 2\", \"guac user 3\" ]\n \\},\n \\{\n \"nome\": \"conn4\",\n \"protocollo\": \"kubernetes\"\n \\}\n]", + "HELP_JSON_MORE_DETAILS" : "L'ID del gruppo di connessione in cui importare la connessione può essere specificato direttamente con un campo \"parentIdentifier\", oppure il percorso al gruppo padre può essere specificato utilizzando un campo \"group\" come mostrato di seguito. È possibile specificare un array di identificatori di utenti e gruppi di utenti a cui concedere l'accesso per ogni connessione.", + "HELP_EXISTING_CONNECTION_MODE" : "Sostituisci/aggiorna completamente le connessioni esistenti se i loro nomi e gruppi di connessione padre corrispondono ai valori nel file fornito. Se questa opzione non è selezionata, il tentativo di importare una connessione con lo stesso nome e gruppo di connessione padre di una connessione esistente verrà considerato un errore..", + "HELP_EXISTING_PERMISSION_MODE" : "Ripristina completamente i permessi concessi per tutte le connessioni nel file fornito, ripristinandoli ai permessi specificati in quel file. Se non vengono specificati permessi, tutti i permessi di connessione pertinenti verranno revocati. Se questa opzione non è selezionata, i permessi esistenti vengono mantenuti e tutti i permessi specificati nel file verranno aggiunti..", + "HELP_SEMICOLON_FOOTNOTE" : "Se presenti, i punti e virgola possono essere preceduti da una barra rovesciata, ad esempio \"nome\\\\;cognome\"", + "HELP_UPLOAD_DROP_TITLE" : "Trascina un file qui", + "HELP_UPLOAD_FILE_TYPES" : "CSV, JSON, o YAML", + "HELP_YAML_DESCRIPTION" : "Un file YAML di importazione della connessione è un elenco di oggetti di connessione con esattamente la stessa struttura del formato JSON.", + "HELP_YAML_EXAMPLE" : "---\n - nome: conn1\n protocollo: vnc\n parametri:\n nome utente: alice\n password: pass1\n nome host: conn1.web.com\n gruppo: ROOT\n utenti:\n - guac utente 1\n - guac utente 2\n gruppi:\n - Utenti Connessione 1\n attributi:\n guacd-encryption: none\n - nome: conn2\n protocollo: rdp\n parametri:\n nome utente: bob\n password: pass2\n nome host: conn2.web.com\n gruppo: ROOT/Gruppo padre\n utenti:\n - guac utente 1\n attributi:\n guacd-encryption: none\n - nome: conn3\n protocollo: ssh\n parametri:\n nome utente: carol\n password: pass3\n nome host: conn3.web.com\n gruppo: ROOT/Gruppo padre/Gruppo figlio\n utenti:\n - guac utente 2\n - guac utente 3\n - nome: conn4\n protocollo: kubernetes", + + "INFO_CONNECTIONS_IMPORTED_SUCCESS" : "{NUMBER} {NUMBER, plural, one{connessione} other{connessioni}} importata/e con successo.", + + "SECTION_HEADER_CONNECTION_IMPORT" : "Importazione della connessione", + "SECTION_HEADER_HELP_CONNECTION_IMPORT_FILE" : "Formato file di importazione della connessione", + "SECTION_HEADER_CSV" : "Formato CSV", + "SECTION_HEADER_JSON" : "Formato JSON", + "SECTION_HEADER_YAML" : "Formato YAML", + + "TABLE_HEADER_ERRORS" : "Errori", + "TABLE_HEADER_GROUP" : "Gruppo", + "TABLE_HEADER_NAME" : "Nome", + "TABLE_HEADER_PROTOCOL" : "Protocollo", + "TABLE_HEADER_ROW_NUMBER" : "Riga #" + }, + + "DATA_SOURCE_DEFAULT" : { + "NAME" : "Predefinito (XML)" }, "FORM" : { + "FIELD_PLACEHOLDER_DATE" : "DD-MM-YYYY", + "FIELD_PLACEHOLDER_TIME" : "HH:MM:SS", + "HELP_SHOW_PASSWORD" : "Fare clic per mostrare la password", "HELP_HIDE_PASSWORD" : "Fare clic per nascondere la password" @@ -150,6 +285,12 @@ }, + "LIST" : { + + "TEXT_ANONYMOUS_USER" : "Anonimo" + + }, + "LOGIN": { "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", @@ -194,7 +335,8 @@ "TABLE_HEADER_HISTORY_START" : "Ora di inizio", "TABLE_HEADER_HISTORY_DURATION" : "Durata", - "TEXT_CONFIRM_DELETE" : "Le Connessioni non possono essere ripristinate dopo la loro eliminazione. Sei sicuro di volere eliminare questa connessione?" + "TEXT_CONFIRM_DELETE" : "Le Connessioni non possono essere ripristinate dopo la loro eliminazione. Sei sicuro di volere eliminare questa connessione?", + "TEXT_HISTORY_DURATION" : "@:APP.TEXT_HISTORY_DURATION" }, @@ -202,6 +344,7 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", @@ -229,11 +372,16 @@ "ACTION_DELETE" : "@:APP.ACTION_DELETE", "ACTION_SAVE" : "@:APP.ACTION_SAVE", + "DIALOG_HEADER_CONFIRM_DELETE" : "Elimina profilo di condivisione", "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", "FIELD_HEADER_NAME" : "Nome:", + "FIELD_HEADER_PRIMARY_CONNECTION" : "Connessione primaria:", + + "SECTION_HEADER_EDIT_SHARING_PROFILE" : "Modifica profilo di condivisione", + "SECTION_HEADER_PARAMETERS" : "Parametri", - "SECTION_HEADER_PARAMETERS" : "Parametri" + "TEXT_CONFIRM_DELETE" : "I profili di condivisione non possono essere ripristinati dopo essere stati eliminati. Vuoi davvero eliminare questo profilo di condivisione?" }, @@ -250,113 +398,377 @@ "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", - "FIELD_HEADER_ADMINISTER_SYSTEM" : "Amministratore di sistema:", + "FIELD_HEADER_ADMINISTER_SYSTEM" : "Amministartore di sistema:", + "FIELD_HEADER_AUDIT_SYSTEM" : "Verifica conformità:", "FIELD_HEADER_CHANGE_OWN_PASSWORD" : "Cambia la tua password:", "FIELD_HEADER_CREATE_NEW_USERS" : "Crea un utente:", + "FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "Crea nuovi gruppi di utenti:", "FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "Crea una connessione:", - "FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "Crea un gruppo di connessioni:", + "FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "Crea un ruppo di connessioni:", + "FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "Crea nuovi profili di condivisione:", "FIELD_HEADER_PASSWORD" : "@:APP.FIELD_HEADER_PASSWORD", "FIELD_HEADER_PASSWORD_AGAIN" : "@:APP.FIELD_HEADER_PASSWORD_AGAIN", + "FIELD_HEADER_USER_DISABLED" : "Accesso disabilitato:", "FIELD_HEADER_USERNAME" : "Nome utente:", "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + + "HELP_NO_USER_GROUPS" : "Questo utente non appartiene attualmente ad alcun gruppo. Espandi questa sezione per aggiungere gruppi..", - "SECTION_HEADER_CONNECTIONS" : "Connessioni", - "SECTION_HEADER_EDIT_USER" : "Modifica Utente", - "SECTION_HEADER_PERMISSIONS" : "Permessi", + "INFO_READ_ONLY" : "Spiacenti, ma questo account utente non può essere modificato.", + "INFO_NO_USER_GROUPS_AVAILABLE" : "Non ci sono gruppi disponibili.", + + "SECTION_HEADER_CONNECTIONS" : "Connessioni", + "SECTION_HEADER_EDIT_USER" : "Modifica Utente", + "SECTION_HEADER_PERMISSIONS" : "Permessi", + "SECTION_HEADER_ALL_CONNECTIONS" : "Tutte le connessioni", + "SECTION_HEADER_CURRENT_CONNECTIONS" : "Connessioni attuali", + "SECTION_HEADER_USER_GROUPS" : "Gruppi", "TEXT_CONFIRM_DELETE" : "L'utente non può essere ripristinato dopo l'eliminazione. Sei sicuro di volere eliminare l'utente?" }, - + + "MANAGE_USER_GROUP" : { + + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_CLONE" : "@:APP.ACTION_CLONE", + "ACTION_DELETE" : "@:APP.ACTION_DELETE", + "ACTION_SAVE" : "@:APP.ACTION_SAVE", + + "DIALOG_HEADER_CONFIRM_DELETE" : "Eliminare il gruppo", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + + "FIELD_HEADER_ADMINISTER_SYSTEM" : "@:MANAGE_USER.FIELD_HEADER_ADMINISTER_SYSTEM", + "FIELD_HEADER_CHANGE_OWN_PASSWORD" : "@:MANAGE_USER.FIELD_HEADER_CHANGE_OWN_PASSWORD", + "FIELD_HEADER_CREATE_NEW_USERS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USERS", + "FIELD_HEADER_CREATE_NEW_USER_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_USER_GROUPS", + "FIELD_HEADER_CREATE_NEW_CONNECTIONS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTIONS", + "FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_CONNECTION_GROUPS", + "FIELD_HEADER_CREATE_NEW_SHARING_PROFILES" : "@:MANAGE_USER.FIELD_HEADER_CREATE_NEW_SHARING_PROFILES", + "FIELD_HEADER_USER_GROUP_DISABLED" : "Disabilitato:", + "FIELD_HEADER_USER_GROUP_NAME" : "Nome del gruppo:", + + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + + "HELP_NO_USER_GROUPS" : "Questo gruppo non appartiene attualmente ad alcun gruppo. Espandi questa sezione per aggiungere gruppi.", + "HELP_NO_MEMBER_USER_GROUPS" : "Questo gruppo al momento non contiene alcun gruppo. Espandi questa sezione per aggiungere gruppi.", + "HELP_NO_MEMBER_USERS" : "Questo gruppo al momento non contiene utenti. Espandi questa sezione per aggiungere utenti.", + + "INFO_READ_ONLY" : "Spiacenti, questo gruppo non può essere modificato.", + "INFO_NO_USER_GROUPS_AVAILABLE" : "@:MANAGE_USER.INFO_NO_USER_GROUPS_AVAILABLE", + "INFO_NO_USERS_AVAILABLE" : "Nessun utente disponibile.", + + "SECTION_HEADER_ALL_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_ALL_CONNECTIONS", + "SECTION_HEADER_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CONNECTIONS", + "SECTION_HEADER_CURRENT_CONNECTIONS" : "@:MANAGE_USER.SECTION_HEADER_CURRENT_CONNECTIONS", + "SECTION_HEADER_EDIT_USER_GROUP" : "Modificare il gruppo", + "SECTION_HEADER_MEMBER_USERS" : "Utenti associati", + "SECTION_HEADER_MEMBER_USER_GROUPS" : "Gruppi associati", + "SECTION_HEADER_PERMISSIONS" : "@:MANAGE_USER.SECTION_HEADER_PERMISSIONS", + "SECTION_HEADER_USER_GROUPS" : "Gruppo principale", + + "TEXT_CONFIRM_DELETE" : "I gruppi non possono essere ripristinati dopo essere stati eliminati. Vuoi davvero eliminare questo gruppo?" + + }, + + "PLAYER" : { + + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_PAUSE" : "@:APP.ACTION_PAUSE", + "ACTION_PLAY" : "@:APP.ACTION_PLAY", + "ACTION_SHOW_KEY_LOG" : "Registro dei tasti premuti", + + "INFO_FRAME_EVENTS_LEGEND" : "Attività sullo schermo", + "INFO_KEY_EVENTS_LEGEND" : "Attività della tastiera", + "INFO_LOADING_RECORDING" : "La registrazione è in fase di caricamento. Attendi...", + "INFO_NO_KEY_LOG" : "Registro dei tasti premuti non disponibile", + "INFO_NUMBER_OF_RESULTS" : "{RESULTS} {RESULTS, plural, one{Corrispondenza} other{Corrispondenze}}", + "INFO_SEEK_IN_PROGRESS" : "Stiamo cercando la posizione richiesta. Attendi...", + + "FIELD_PLACEHOLDER_TEXT_BATCH_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER" + + }, + + "PROTOCOL_KUBERNETES" : { + + "FIELD_HEADER_BACKSPACE" : "Invia con il tasto Backspace:", + "FIELD_HEADER_CA_CERT" : "Certificato dell'autorità di certificazione:", + "FIELD_HEADER_CLIENT_CERT" : "Certificato client:", + "FIELD_HEADER_CLIENT_KEY" : "Chiave client:", + "FIELD_HEADER_COLOR_SCHEME" : "Schema colori:", + "FIELD_HEADER_CONTAINER" : "Nome contenitore:", + "FIELD_HEADER_CREATE_RECORDING_PATH" : "Crea automaticamente il percorso di registrazione:", + "FIELD_HEADER_CREATE_TYPESCRIPT_PATH" : "Crea automaticamente il percorso TypeScript:", + "FIELD_HEADER_EXEC_COMMAND" : "Comando (exec):", + "FIELD_HEADER_FONT_NAME" : "Nome carattere:", + "FIELD_HEADER_FONT_SIZE" : "Dimensione carattere:", + "FIELD_HEADER_HOSTNAME" : "Nome host:", + "FIELD_HEADER_IGNORE_CERT" : "Ignora certificato server:", + "FIELD_HEADER_NAMESPACE" : "Spazio dei nomi:", + "FIELD_HEADER_POD" : "Nome pod:", + "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_READ_ONLY" : "Sola lettura:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Escludi mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Escludi grafica/flussi:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Includi eventi chiave:", + "FIELD_HEADER_RECORDING_NAME" : "Nome registrazione:", + "FIELD_HEADER_RECORDING_PATH" : "Percorso registrazione:", + "FIELD_HEADER_SCROLLBACK" : "Dimensione massima scrollback:", + "FIELD_HEADER_TYPESCRIPT_NAME" : "Nome TypeScript:", + "FIELD_HEADER_TYPESCRIPT_PATH" : "Percorso TypeScript:", + "FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING" : "@:APP.FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING", + "FIELD_HEADER_USE_SSL" : "Usa SSL/TLS", + + "FIELD_OPTION_BACKSPACE_EMPTY" : "", + "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", + "FIELD_OPTION_BACKSPACE_127" : "Canc (Ctrl-?)", + + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Nero su bianco", + "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", + "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Grigio su nero", + "FIELD_OPTION_COLOR_SCHEME_GREEN_BLACK" : "Verde su nero", + "FIELD_OPTION_COLOR_SCHEME_WHITE_BLACK" : "Bianco su nero", + + "FIELD_OPTION_FONT_SIZE_8" : "8", + "FIELD_OPTION_FONT_SIZE_9" : "9", + "FIELD_OPTION_FONT_SIZE_10" : "10", + "FIELD_OPTION_FONT_SIZE_11" : "11", + "FIELD_OPTION_FONT_SIZE_12" : "12", + "FIELD_OPTION_FONT_SIZE_14" : "14", + "FIELD_OPTION_FONT_SIZE_18" : "18", + "FIELD_OPTION_FONT_SIZE_24" : "24", + "FIELD_OPTION_FONT_SIZE_30" : "30", + "FIELD_OPTION_FONT_SIZE_36" : "36", + "FIELD_OPTION_FONT_SIZE_48" : "48", + "FIELD_OPTION_FONT_SIZE_60" : "60", + "FIELD_OPTION_FONT_SIZE_72" : "72", + "FIELD_OPTION_FONT_SIZE_96" : "96", + "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + + "NAME" : "Kubernetes", + + "SECTION_HEADER_AUTHENTICATION" : "Autenticazione", + "SECTION_HEADER_BEHAVIOR" : "Comportamento del terminale", + "SECTION_HEADER_CONTAINER" : "Contenitore", + "SECTION_HEADER_DISPLAY" : "Visualizzazione", + "SECTION_HEADER_RECORDING" : "Registrazione dello schermo", + "SECTION_HEADER_TYPESCRIPT" : "Typescript (Registrazione sessione di testo)", + "SECTION_HEADER_NETWORK" : "Rete" + }, + "PROTOCOL_RDP" : { - "FIELD_HEADER_COLOR_DEPTH" : "Profondità di colore:", - "FIELD_HEADER_CONSOLE" : "Console amministratore:", - "FIELD_HEADER_CONSOLE_AUDIO" : "Supporta l'audio nella console:", - "FIELD_HEADER_CLIENT_NAME" : "Nome Client:", - "FIELD_HEADER_DISABLE_AUDIO" : "Disattiva audio:", - "FIELD_HEADER_DISABLE_AUTH" : "Disabilita autenticazione:", - "FIELD_HEADER_DOMAIN" : "Dominio:", - "FIELD_HEADER_DPI" : "Risoluzione (DPI):", - "FIELD_HEADER_DRIVE_PATH" : "Percorso Drive:", + "FIELD_HEADER_CERT_TOFU" : "Certificato host attendibile al primo utilizzo:", + "FIELD_HEADER_CERT_FINGERPRINTS" : "Impronte digitali dei certificati host attendibili:", + "FIELD_HEADER_CLIENT_NAME" : "Nome client:", + "FIELD_HEADER_COLOR_DEPTH" : "Profondità colore:", + "FIELD_HEADER_CONSOLE" : "Console di amministrazione:", + "FIELD_HEADER_CONSOLE_AUDIO" : "Supporta l'audio nella console:", + "FIELD_HEADER_CREATE_DRIVE_PATH" : "Crea automaticamente l'unità:", + "FIELD_HEADER_CREATE_RECORDING_PATH" : "Crea automaticamente il percorso di registrazione:", + "FIELD_HEADER_DISABLE_AUDIO" : "Disabilita l'audio:", + "FIELD_HEADER_DISABLE_AUTH" : "Disabilita autenticazione:", + "FIELD_HEADER_DISABLE_COPY" : "Disabilita copia da desktop remoto:", + "FIELD_HEADER_DISABLE_DOWNLOAD" : "Disabilita download file:", + "FIELD_HEADER_DISABLE_PASTE" : "Disabilita incolla dal client:", + "FIELD_HEADER_DISABLE_UPLOAD" : "Disabilita caricamento file:", + "FIELD_HEADER_DOMAIN" : "Dominio:", + "FIELD_HEADER_DPI" : "Risoluzione (DPI):", + "FIELD_HEADER_DRIVE_NAME" : "Nome unità:", + "FIELD_HEADER_DRIVE_PATH" : "Percorso unità:", + "FIELD_HEADER_ENABLE_AUDIO_INPUT" : "Abilita ingresso audio (microfono):", "FIELD_HEADER_ENABLE_DESKTOP_COMPOSITION" : "Abilita composizione desktop (Aero):", - "FIELD_HEADER_ENABLE_DRIVE" : "Abilita drive:", - "FIELD_HEADER_ENABLE_FONT_SMOOTHING" : "Abilita l'arrotondamento dei caratteri (ClearType):", - "FIELD_HEADER_ENABLE_FULL_WINDOW_DRAG" : "Abilita il trascinamento della finestra intera:", - "FIELD_HEADER_ENABLE_MENU_ANIMATIONS" : "Abilita le animazioni del menu:", + "FIELD_HEADER_ENABLE_DRIVE" : "Abilita unità:", + "FIELD_HEADER_ENABLE_FONT_SMOOTHING" : "Abilita smussatura font (ClearType):", + "FIELD_HEADER_ENABLE_FULL_WINDOW_DRAG" : "Abilita trascinamento a finestra intera:", + "FIELD_HEADER_ENABLE_MENU_ANIMATIONS" : "Abilita animazioni menu:", + "FIELD_HEADER_DISABLE_BITMAP_CACHING" : "Disabilita memorizzazione nella cache bitmap:", + "FIELD_HEADER_DISABLE_OFFSCREEN_CACHING" : "Disabilita memorizzazione nella cache fuori schermo:", + "FIELD_HEADER_DISABLE_GLYPH_CACHING" : "Disabilita la memorizzazione nella cache dei glifi:", + "FIELD_HEADER_DISABLE_GFX" : "Disabilita l'estensione della pipeline grafica:", "FIELD_HEADER_ENABLE_PRINTING" : "Abilita la stampa:", "FIELD_HEADER_ENABLE_SFTP" : "Abilita SFTP:", - "FIELD_HEADER_ENABLE_THEMING" : "Abilita temi:", - "FIELD_HEADER_ENABLE_WALLPAPER" : "Abilita sfondo:", - "FIELD_HEADER_HEIGHT" : "Altezza:", - "FIELD_HEADER_HOSTNAME" : "Nome host:", - "FIELD_HEADER_IGNORE_CERT" : "Ignora certificato server:", - "FIELD_HEADER_INITIAL_PROGRAM" : "Programma iniziale:", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PORT" : "Porta:", - "FIELD_HEADER_REMOTE_APP_ARGS" : "Parametri:", - "FIELD_HEADER_REMOTE_APP_DIR" : "Cartella di lavoro:", - "FIELD_HEADER_REMOTE_APP" : "Programma:", - "FIELD_HEADER_SECURITY" : "Modalità di sicurezza:", - "FIELD_HEADER_SERVER_LAYOUT" : "Layout della tastiera:", - "FIELD_HEADER_SFTP_HOSTNAME" : "Nome host:", - "FIELD_HEADER_SFTP_PASSPHRASE" : "Passphrase:", - "FIELD_HEADER_SFTP_PASSWORD" : "Password:", - "FIELD_HEADER_SFTP_PORT" : "Porta:", - "FIELD_HEADER_SFTP_PRIVATE_KEY" : "Chiave privata:", - "FIELD_HEADER_SFTP_USERNAME" : "Nome utente:", - "FIELD_HEADER_STATIC_CHANNELS" : "Nomi dei canali statici:", - "FIELD_HEADER_USERNAME" : "Nome utente:", - "FIELD_HEADER_WIDTH" : "Larghezza:", - - "FIELD_OPTION_COLOR_DEPTH_16" : "Low color (16-bit)", - "FIELD_OPTION_COLOR_DEPTH_24" : "True color (24-bit)", - "FIELD_OPTION_COLOR_DEPTH_32" : "True color (32-bit)", - "FIELD_OPTION_COLOR_DEPTH_8" : "256 color", + "FIELD_HEADER_ENABLE_THEMING" : "Abilita i temi:", + "FIELD_HEADER_ENABLE_TOUCH" : "Abilita il multi-touch:", + "FIELD_HEADER_ENABLE_WALLPAPER" : "Abilita lo sfondo:", + "FIELD_HEADER_FORCE_LOSSLESS" : "Forza la compressione senza perdite:", + "FIELD_HEADER_GATEWAY_DOMAIN" : "Dominio:", + "FIELD_HEADER_GATEWAY_HOSTNAME" : "Nome host:", + "FIELD_HEADER_GATEWAY_PASSWORD" : "Password:", + "FIELD_HEADER_GATEWAY_PORT" : "Porta:", + "FIELD_HEADER_GATEWAY_USERNAME" : "Nome utente:", + "FIELD_HEADER_HEIGHT" : "Altezza:", + "FIELD_HEADER_HOSTNAME" : "Nome host:", + "FIELD_HEADER_IGNORE_CERT" : "Ignora certificato server:", + "FIELD_HEADER_INITIAL_PROGRAM" : "Programma iniziale:", + "FIELD_HEADER_LOAD_BALANCE_INFO" : "Informazioni sul bilanciamento del carico/cookie:", + "FIELD_HEADER_NORMALIZE_CLIPBOARD" : "Finali di riga:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_PRINTER_NAME" : "Nome stampante reindirizzata:", + "FIELD_HEADER_PRECONNECTION_BLOB" : "BLOB di preconnessione (ID VM):", + "FIELD_HEADER_PRECONNECTION_ID" : "ID sorgente RDP:", + "FIELD_HEADER_READ_ONLY" : "Sola lettura:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Escludi mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Escludi grafica/flussi:", + "FIELD_HEADER_RECORDING_EXCLUDE_TOUCH" : "Escludi eventi touch:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Includi eventi tasto:", + "FIELD_HEADER_RECORDING_NAME" : "Nome registrazione:", + "FIELD_HEADER_RECORDING_PATH" : "Percorso registrazione:", + "FIELD_HEADER_RESIZE_METHOD" : "Metodo di ridimensionamento:", + "FIELD_HEADER_REMOTE_APP_ARGS" : "Parametri:", + "FIELD_HEADER_REMOTE_APP_DIR" : "Directory di lavoro:", + "FIELD_HEADER_REMOTE_APP" : "Programma:", + "FIELD_HEADER_SECURITY" : "Modalità di sicurezza:", + "FIELD_HEADER_SERVER_LAYOUT" : "Layout tastiera:", + "FIELD_HEADER_SFTP_DIRECTORY" : "Directory di caricamento predefinita:", + "FIELD_HEADER_SFTP_DISABLE_DOWNLOAD" : "Disabilita download file:", + "FIELD_HEADER_SFTP_DISABLE_UPLOAD" : "Disabilita caricamento file:", + "FIELD_HEADER_SFTP_HOST_KEY" : "Chiave host pubblica (Base64):", + "FIELD_HEADER_SFTP_HOSTNAME" : "Nome host:", + "FIELD_HEADER_SFTP_SERVER_ALIVE_INTERVAL" : "Intervallo keepalive SFTP:", + "FIELD_HEADER_SFTP_PASSPHRASE" : "Password:", + "FIELD_HEADER_SFTP_PASSWORD" : "Password:", + "FIELD_HEADER_SFTP_PORT" : "Porta:", + "FIELD_HEADER_SFTP_PRIVATE_KEY" : "Chiave privata:", + "FIELD_HEADER_SFTP_PUBLIC_KEY" : "Chiave pubblica:", + "FIELD_HEADER_SFTP_ROOT_DIRECTORY" : "Directory radice del browser file:", + "FIELD_HEADER_SFTP_TIMEOUT" : "Timeout della connessione SFTP:", + "FIELD_HEADER_SFTP_USERNAME" : "Nome utente:", + "FIELD_HEADER_STATIC_CHANNELS" : "Nomi dei canali statici:", + "FIELD_HEADER_TIMEOUT" : "Timeout della connessione", + "FIELD_HEADER_TIMEZONE" : "Fuso orario:", + "FIELD_HEADER_USERNAME" : "Nome utente:", + "FIELD_HEADER_WIDTH" : "Larghezza:", + "FIELD_HEADER_WOL_BROADCAST_ADDR" : "Indirizzo broadcast per pacchetto WoL:", + "FIELD_HEADER_WOL_MAC_ADDR" : "Indirizzo MAC dell'host remoto:", + "FIELD_HEADER_WOL_SEND_PACKET" : "Invia pacchetto WoL:", + "FIELD_HEADER_WOL_UDP_PORT" : "Porta UDP per pacchetto WoL:", + "FIELD_HEADER_WOL_WAIT_TIME" : "Tempo di attesa per l'avvio dell'host:", + + "FIELD_OPTION_NORMALIZE_CLIPBOARD_EMPTY" : "", + "FIELD_OPTION_NORMALIZE_CLIPBOARD_PRESERVE" : "Mantieni così com'è", + "FIELD_OPTION_NORMALIZE_CLIPBOARD_UNIX" : "Linux/Mac/Unix (LF)", + "FIELD_OPTION_NORMALIZE_CLIPBOARD_WINDOWS" : "Windows (CRLF)", + + "FIELD_OPTION_COLOR_DEPTH_16" : "Basso colore (16 bit)", + "FIELD_OPTION_COLOR_DEPTH_24" : "True color (24 bit)", + "FIELD_OPTION_COLOR_DEPTH_32" : "True color (32 bit)", + "FIELD_OPTION_COLOR_DEPTH_8" : "256 colori", "FIELD_OPTION_COLOR_DEPTH_EMPTY" : "", - "FIELD_OPTION_SECURITY_ANY" : "Qualsiasi", - "FIELD_OPTION_SECURITY_EMPTY" : "", - "FIELD_OPTION_SECURITY_NLA" : "NLA (Network Level Authentication)", - "FIELD_OPTION_SECURITY_RDP" : "RDP encryption", - "FIELD_OPTION_SECURITY_TLS" : "TLS encryption", - - "FIELD_OPTION_SERVER_LAYOUT_DE_DE_QWERTZ" : "German (Qwertz)", - "FIELD_OPTION_SERVER_LAYOUT_EMPTY" : "", - "FIELD_OPTION_SERVER_LAYOUT_EN_US_QWERTY" : "US English (Qwerty)", - "FIELD_OPTION_SERVER_LAYOUT_FAILSAFE" : "Unicode", - "FIELD_OPTION_SERVER_LAYOUT_FR_FR_AZERTY" : "French (Azerty)", - "FIELD_OPTION_SERVER_LAYOUT_IT_IT_QWERTY" : "Italiana (Qwerty)", - "FIELD_OPTION_SERVER_LAYOUT_PL_PL_QWERTY" : "Polacca (Qwerty)", - "FIELD_OPTION_SERVER_LAYOUT_RO_RO_QWERTY" : "Rumeno (Qwerty)", - "FIELD_OPTION_SERVER_LAYOUT_SV_SE_QWERTY" : "Swedish (Qwerty)", + "FIELD_OPTION_RESIZE_METHOD_DISPLAY_UPDATE": "Canale virtuale \"Aggiornamento display\" (RDP 8.1+)", + "FIELD_OPTION_RESIZE_METHOD_EMPTY" : "", + "FIELD_OPTION_RESIZE_METHOD_RECONNECT" : "Riconnetti", + + "FIELD_OPTION_SECURITY_ANY" : "Qualsiasi", + "FIELD_OPTION_SECURITY_EMPTY" : "", + "FIELD_OPTION_SECURITY_NLA" : "NLA (Autenticazione a livello di rete)", + "FIELD_OPTION_SECURITY_RDP" : "Crittografia RDP", + "FIELD_OPTION_SECURITY_TLS" : "Crittografia TLS", + "FIELD_OPTION_SECURITY_VMCONNECT" : "Hyper-V / VMConnect", + + "FIELD_OPTION_SERVER_LAYOUT_CS_CZ_QWERTZ" : "Ceco (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_DA_DK_QWERTY" : "Danese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_DE_CH_QWERTZ" : "Tedesco svizzero (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_DE_DE_QWERTZ" : "Tedesco (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_EMPTY" : "", + "FIELD_OPTION_SERVER_LAYOUT_EN_GB_QWERTY" : "Inglese Regno Unito (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_EN_US_QWERTY" : "Inglese Stati Uniti (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_ES_ES_QWERTY" : "Spagnolo (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_ES_LATAM_QWERTY" : "Latinoamericano (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_FAILSAFE" : "Unicode", + "FIELD_OPTION_SERVER_LAYOUT_FR_BE_AZERTY" : "Francese belga (Azerty)", + "FIELD_OPTION_SERVER_LAYOUT_FR_CA_QWERTY" : "Francese canadese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_FR_CH_QWERTZ" : "Francese svizzero (Qwertz)", + "FIELD_OPTION_SERVER_LAYOUT_FR_FR_AZERTY" : "Francese (Azerty)", + "FIELD_OPTION_SERVER_LAYOUT_HU_HU_QWERTZ" : "Ungherese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_IT_IT_QWERTY" : "Italiano (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_JA_JP_QWERTY" : "Giapponese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_NO_NO_QWERTY" : "Norvegese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_PL_PL_QWERTY" : "Polacco (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_PT_BR_QWERTY" : "Portoghese brasiliano (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_PT_PT_QWERTY" : "Portoghese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_RO_RO_QWERTY" : "Rumeno (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_SV_SE_QWERTY" : "Svedese (Qwerty)", + "FIELD_OPTION_SERVER_LAYOUT_TR_TR_QWERTY" : "Turco-Q (Qwerty)", "NAME" : "RDP", - "SECTION_HEADER_AUTHENTICATION" : "Autenticazione", - "SECTION_HEADER_BASIC_PARAMETERS" : "Impostazioni di base", - "SECTION_HEADER_CLIPBOARD" : "Appunti", - "SECTION_HEADER_DEVICE_REDIRECTION" : "Reindirizzamento del dispositivo", - "SECTION_HEADER_DISPLAY" : "Schermo", - "SECTION_HEADER_NETWORK" : "Rete", - "SECTION_HEADER_PERFORMANCE" : "Prestazione", - "SECTION_HEADER_REMOTEAPP" : "RemoteApp", - "SECTION_HEADER_SFTP" : "SFTP" - + "SECTION_HEADER_AUTHENTICATION" : "Autenticazione", + "SECTION_HEADER_BASIC_PARAMETERS" : "Impostazioni di base", + "SECTION_HEADER_CLIPBOARD" : "Appunti", + "SECTION_HEADER_DEVICE_REDIRECTION" : "Reindirizzamento dispositivo", + "SECTION_HEADER_DISPLAY" : "Schermo", + "SECTION_HEADER_GATEWAY" : "Gateway Desktop remoto", + "SECTION_HEADER_LOAD_BALANCING" : "Bilanciamento del carico", + "SECTION_HEADER_NETWORK" : "Rete", + "SECTION_HEADER_PERFORMANCE" : "Prestazioni", + "SECTION_HEADER_PRECONNECTION_PDU" : "PDU di preconnessione / Hyper-V", + "SECTION_HEADER_RECORDING" : "Registrazione schermo", + "SECTION_HEADER_REMOTEAPP" : "RemoteApp", + "SECTION_HEADER_SFTP" : "SFTP", + "SECTION_HEADER_WOL" : "Wake-on-LAN (WoL)" }, "PROTOCOL_SSH" : { - "FIELD_HEADER_FONT_NAME" : "Nome carattere:", - "FIELD_HEADER_FONT_SIZE" : "Dimensione del carattere:", - "FIELD_HEADER_ENABLE_SFTP" : "Abilita SFTP:", - "FIELD_HEADER_HOSTNAME" : "Nome host:", - "FIELD_HEADER_USERNAME" : "Nome utente:", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PASSPHRASE" : "Passphrase:", - "FIELD_HEADER_PORT" : "Porta:", - "FIELD_HEADER_PRIVATE_KEY" : "Chiave privata:", + "FIELD_HEADER_BACKSPACE" : "Invia con il tasto Backspace:", + "FIELD_HEADER_COLOR_SCHEME" : "Schema colori:", + "FIELD_HEADER_COMMAND" : "Esegui comando:", + "FIELD_HEADER_CREATE_RECORDING_PATH" : "Crea automaticamente il percorso di registrazione:", + "FIELD_HEADER_CREATE_TYPESCRIPT_PATH" : "Crea automaticamente il percorso TypeScript:", + "FIELD_HEADER_DISABLE_COPY" : "Disabilita la copia dal terminale:", + "FIELD_HEADER_DISABLE_PASTE" : "Disabilita la copia dal client:", + "FIELD_HEADER_FONT_NAME" : "Nome del font:", + "FIELD_HEADER_FONT_SIZE" : "Dimensione del font:", + "FIELD_HEADER_ENABLE_SFTP" : "Abilita SFTP:", + "FIELD_HEADER_HOST_KEY" : "Chiave host pubblica (Base64):", + "FIELD_HEADER_HOSTNAME" : "Nome host:", + "FIELD_HEADER_LOCALE" : "Lingua/Impostazioni locali ($LANG):", + "FIELD_HEADER_USERNAME" : "Nome utente:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PASSPHRASE" : "Passphrase:", + "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_PRIVATE_KEY" : "Chiave privata:", + "FIELD_HEADER_PUBLIC_KEY" : "Chiave pubblica:", + "FIELD_HEADER_SCROLLBACK" : "Dimensione massima scrollback:", + "FIELD_HEADER_READ_ONLY" : "Sola lettura:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Escludi mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Escludi grafica/flussi:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Includi eventi chiave:", + "FIELD_HEADER_RECORDING_NAME" : "Nome registrazione:", + "FIELD_HEADER_RECORDING_PATH" : "Registrazione percorso:", + "FIELD_HEADER_SERVER_ALIVE_INTERVAL" : "Intervallo di keepalive del server:", + "FIELD_HEADER_SFTP_DISABLE_DOWNLOAD" : "Disabilita download file:", + "FIELD_HEADER_SFTP_ROOT_DIRECTORY" : "Directory radice del browser file:", + "FIELD_HEADER_SFTP_DISABLE_UPLOAD" : "Disabilita caricamento file:", + "FIELD_HEADER_TERMINAL_TYPE" : "Tipo di terminale:", + "FIELD_HEADER_TIMEOUT" : "Timeout di connessione:", + "FIELD_HEADER_TIMEZONE" : "Fuso orario ($TZ):", + "FIELD_HEADER_TYPESCRIPT_NAME" : "Nome TypeScript:", + "FIELD_HEADER_TYPESCRIPT_PATH" : "TypeScript percorso:", + "FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING": "@:APP.FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING", + "FIELD_HEADER_WOL_BROADCAST_ADDR" : "Indirizzo broadcast per pacchetto WoL:", + "FIELD_HEADER_WOL_MAC_ADDR" : "Indirizzo MAC dell'host remoto:", + "FIELD_HEADER_WOL_SEND_PACKET" : "Invia pacchetto WoL:", + "FIELD_HEADER_WOL_UDP_PORT" : "Porta UDP per pacchetto WoL:", + "FIELD_HEADER_WOL_WAIT_TIME" : "Tempo di attesa per l'avvio dell'host:", + + "FIELD_OPTION_BACKSPACE_EMPTY" : "", + "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", + "FIELD_OPTION_BACKSPACE_127" : "Canc (Ctrl-?)", + + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Nero su bianco", + "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", + "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Grigio su nero", + "FIELD_OPTION_COLOR_SCHEME_GREEN_BLACK" : "Verde su nero", + "FIELD_OPTION_COLOR_SCHEME_WHITE_BLACK" : "Bianco su nero", "FIELD_OPTION_FONT_SIZE_8" : "8", "FIELD_OPTION_FONT_SIZE_9" : "9", @@ -374,25 +786,75 @@ "FIELD_OPTION_FONT_SIZE_96" : "96", "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + "FIELD_OPTION_TERMINAL_TYPE_ANSI" : "ansi", + "FIELD_OPTION_TERMINAL_TYPE_EMPTY" : "", + "FIELD_OPTION_TERMINAL_TYPE_LINUX" : "linux", + "FIELD_OPTION_TERMINAL_TYPE_VT100" : "vt100", + "FIELD_OPTION_TERMINAL_TYPE_VT220" : "vt220", + "FIELD_OPTION_TERMINAL_TYPE_XTERM" : "xterm", + "FIELD_OPTION_TERMINAL_TYPE_XTERM_256COLOR" : "xterm-256color", + "NAME" : "SSH", "SECTION_HEADER_AUTHENTICATION" : "Autenticazione", + "SECTION_HEADER_BEHAVIOR" : "Comportamento del terminale", "SECTION_HEADER_CLIPBOARD" : "Appunti", "SECTION_HEADER_DISPLAY" : "Schermo", "SECTION_HEADER_NETWORK" : "Rete", - "SECTION_HEADER_SFTP" : "SFTP" + "SECTION_HEADER_RECORDING" : "Registrazione schermo", + "SECTION_HEADER_SESSION" : "Sessione/Ambiente", + "SECTION_HEADER_TYPESCRIPT" : "Typescript (Registrazione sessione di testo)", + "SECTION_HEADER_SFTP" : "SFTP", + "SECTION_HEADER_WOL" : "Wake-on-LAN (WoL)" }, "PROTOCOL_TELNET" : { - "FIELD_HEADER_FONT_NAME" : "Nome carattere:", - "FIELD_HEADER_FONT_SIZE" : "Dimensione del carattere:", - "FIELD_HEADER_HOSTNAME" : "Nome host:", - "FIELD_HEADER_USERNAME" : "Nome utente:", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PASSWORD_REGEX" : "Espressione regolare della password:", - "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_BACKSPACE" : "Invia con il tasto Backspace:", + "FIELD_HEADER_COLOR_SCHEME" : "Schema colori:", + "FIELD_HEADER_CREATE_RECORDING_PATH" : "Crea automaticamente il percorso di registrazione:", + "FIELD_HEADER_CREATE_TYPESCRIPT_PATH" : "Crea automaticamente il percorso TypeScript:", + "FIELD_HEADER_DISABLE_COPY" : "Disabilita la copia dal terminale:", + "FIELD_HEADER_DISABLE_PASTE" : "Disabilita la copia dal client:", + "FIELD_HEADER_FONT_NAME" : "Nome del font:", + "FIELD_HEADER_FONT_SIZE" : "Dimensione del font:", + "FIELD_HEADER_HOSTNAME" : "Nome host:", + "FIELD_HEADER_LOGIN_FAILURE_REGEX" : "Espressione regolare di errore di accesso:", + "FIELD_HEADER_LOGIN_SUCCESS_REGEX" : "Espressione regolare di successo di accesso:", + "FIELD_HEADER_USERNAME" : "Nome utente:", + "FIELD_HEADER_USERNAME_REGEX" : "Espressione regolare del nome utente:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PASSWORD_REGEX" : "Espressione regolare della password:", + "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_READ_ONLY" : "Sola lettura:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Escludi mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Escludi grafica/flussi:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Includi eventi chiave:", + "FIELD_HEADER_RECORDING_NAME" : "Nome registrazione:", + "FIELD_HEADER_RECORDING_PATH" : "Percorso registrazione:", + "FIELD_HEADER_SCROLLBACK" : "Dimensione massima scrollback:", + "FIELD_HEADER_TERMINAL_TYPE" : "Tipo di terminale:", + "FIELD_HEADER_TIMEOUT" : "Timeout di connessione:", + "FIELD_HEADER_TYPESCRIPT_NAME" : "Nome TypeScript:", + "FIELD_HEADER_TYPESCRIPT_PATH" : "Percorso TypeScript:", + "FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING": "@:APP.FIELD_HEADER_TYPESCRIPT_WRITE_EXISTING", + "FIELD_HEADER_WOL_BROADCAST_ADDR" : "Indirizzo broadcast per pacchetto WoL:", + "FIELD_HEADER_WOL_MAC_ADDR" : "Indirizzo MAC dell'host remoto:", + "FIELD_HEADER_WOL_SEND_PACKET" : "Invia pacchetto WoL:", + "FIELD_HEADER_WOL_UDP_PORT" : "Porta UDP per pacchetto WoL:", + "FIELD_HEADER_WOL_WAIT_TIME" : "Tempo di attesa per l'avvio dell'host:", + + "FIELD_OPTION_BACKSPACE_EMPTY" : "", + "FIELD_OPTION_BACKSPACE_8" : "Backspace (Ctrl-H)", + "FIELD_OPTION_BACKSPACE_127" : "Canc (Ctrl-?)", + + "FIELD_OPTION_COLOR_SCHEME_BLACK_WHITE" : "Nero su bianco", + "FIELD_OPTION_COLOR_SCHEME_EMPTY" : "", + "FIELD_OPTION_COLOR_SCHEME_GRAY_BLACK" : "Grigio su nero", + "FIELD_OPTION_COLOR_SCHEME_GREEN_BLACK" : "Verde su nero", + "FIELD_OPTION_COLOR_SCHEME_WHITE_BLACK" : "Bianco su nero", "FIELD_OPTION_FONT_SIZE_8" : "8", "FIELD_OPTION_FONT_SIZE_9" : "9", @@ -410,47 +872,117 @@ "FIELD_OPTION_FONT_SIZE_96" : "96", "FIELD_OPTION_FONT_SIZE_EMPTY" : "", + "FIELD_OPTION_TERMINAL_TYPE_ANSI" : "ansi", + "FIELD_OPTION_TERMINAL_TYPE_EMPTY" : "", + "FIELD_OPTION_TERMINAL_TYPE_LINUX" : "linux", + "FIELD_OPTION_TERMINAL_TYPE_VT100" : "vt100", + "FIELD_OPTION_TERMINAL_TYPE_VT220" : "vt220", + "FIELD_OPTION_TERMINAL_TYPE_XTERM" : "xterm", + "FIELD_OPTION_TERMINAL_TYPE_XTERM_256COLOR" : "xterm-256color", + "NAME" : "Telnet", "SECTION_HEADER_AUTHENTICATION" : "Autenticazione", + "SECTION_HEADER_BEHAVIOR" : "Comportamento del terminale", "SECTION_HEADER_CLIPBOARD" : "Appunti", - "SECTION_HEADER_DISPLAY" : "Schermo", - "SECTION_HEADER_NETWORK" : "Rete" - + "SECTION_HEADER_DISPLAY" : "Visualizzazione", + "SECTION_HEADER_RECORDING" : "Registrazione dello schermo", + "SECTION_HEADER_TYPESCRIPT" : "Typescript (Registrazione sessione di testo)", + "SECTION_HEADER_NETWORK" : "Rete", + "SECTION_HEADER_WOL" : "Wake-on-LAN (WoL)" }, "PROTOCOL_VNC" : { - "FIELD_HEADER_AUDIO_SERVERNAME" : "Nome del server audio:", - "FIELD_HEADER_COLOR_DEPTH" : "Profondità di colore:", - "FIELD_HEADER_CURSOR" : "Cursore:", - "FIELD_HEADER_DEST_HOST" : "Host di destinazione:", - "FIELD_HEADER_DEST_PORT" : "Porta di destinazione:", - "FIELD_HEADER_ENABLE_AUDIO" : "Abilita audio:", - "FIELD_HEADER_ENABLE_SFTP" : "Abilita SFTP:", - "FIELD_HEADER_HOSTNAME" : "Nome host:", - "FIELD_HEADER_USERNAME" : "Nome utente:", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PORT" : "Porta:", - "FIELD_HEADER_READ_ONLY" : "Sola lettura:", - "FIELD_HEADER_SFTP_HOSTNAME" : "Nome host:", - "FIELD_HEADER_SFTP_PASSPHRASE" : "Passphrase:", - "FIELD_HEADER_SFTP_PASSWORD" : "Password:", - "FIELD_HEADER_SFTP_PORT" : "Porta:", - "FIELD_HEADER_SFTP_PRIVATE_KEY" : "Chiave privata:", - "FIELD_HEADER_SFTP_USERNAME" : "Nome utente:", - "FIELD_HEADER_SWAP_RED_BLUE" : "Scambia componenti rosso/blu:", - - "FIELD_OPTION_COLOR_DEPTH_8" : "256 color", - "FIELD_OPTION_COLOR_DEPTH_16" : "Low color (16-bit)", - "FIELD_OPTION_COLOR_DEPTH_24" : "True color (24-bit)", - "FIELD_OPTION_COLOR_DEPTH_32" : "True color (32-bit)", - "FIELD_OPTION_COLOR_DEPTH_EMPTY" : "", + "FIELD_HEADER_AUDIO_SERVERNAME" : "Nome del server audio:", + "FIELD_HEADER_CLIPBOARD_ENCODING" : "Codifica:", + "FIELD_HEADER_COLOR_DEPTH" : "Profondità colore:", + "FIELD_HEADER_COMPRESS_LEVEL" : "Livello di compressione:", + "FIELD_HEADER_CREATE_RECORDING_PATH" : "Crea automaticamente il percorso di registrazione:", + "FIELD_HEADER_CURSOR" : "Cursore:", + "FIELD_HEADER_DEST_HOST" : "Host di destinazione:", + "FIELD_HEADER_DEST_PORT" : "Porta di destinazione:", + "FIELD_HEADER_DISABLE_COPY" : "Disabilita la copia dal desktop remoto:", + "FIELD_HEADER_DISABLE_DISPLAY_RESIZE" : "Disabilita il ridimensionamento del display remoto:", + "FIELD_HEADER_DISABLE_PASTE" : "Disabilita incolla dal client:", + "FIELD_HEADER_DISABLE_SERVER_INPUT" : "Disabilita input dal server quando il client è connesso:", + "FIELD_HEADER_ENABLE_AUDIO" : "Abilita audio:", + "FIELD_HEADER_ENABLE_SFTP" : "Abilita SFTP:", + "FIELD_HEADER_ENCODINGS" : "Codifiche di visualizzazione:", + "FIELD_HEADER_FORCE_LOSSLESS" : "Forza compressione lossless:", + "FIELD_HEADER_HOSTNAME" : "Nome host:", + "FIELD_HEADER_USERNAME" : "Nome utente:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PORT" : "Porta:", + "FIELD_HEADER_QUALITY_LEVEL" : "Qualità di visualizzazione:", + "FIELD_HEADER_READ_ONLY" : "Sola lettura:", + "FIELD_HEADER_RECORDING_WRITE_EXISTING" : "@:APP.FIELD_HEADER_RECORDING_WRITE_EXISTING", + "FIELD_HEADER_RECORDING_EXCLUDE_MOUSE" : "Escludi mouse:", + "FIELD_HEADER_RECORDING_EXCLUDE_OUTPUT" : "Escludi grafica/flussi:", + "FIELD_HEADER_RECORDING_INCLUDE_KEYS" : "Includi eventi chiave:", + "FIELD_HEADER_RECORDING_NAME" : "Nome della registrazione:", + "FIELD_HEADER_RECORDING_PATH" : "Percorso della registrazione:", + "FIELD_HEADER_SFTP_DIRECTORY" : "Directory di caricamento predefinita:", + "FIELD_HEADER_SFTP_DISABLE_DOWNLOAD" : "Disabilita download file:", + "FIELD_HEADER_SFTP_DISABLE_UPLOAD" : "Disabilita caricamento file:", + "FIELD_HEADER_SFTP_HOST_KEY" : "Chiave host pubblica (Base64):", + "FIELD_HEADER_SFTP_HOSTNAME" : "Nome host:", + "FIELD_HEADER_SFTP_SERVER_ALIVE_INTERVAL" : "Intervallo keepalive SFTP:", + "FIELD_HEADER_SFTP_PASSPHRASE" : "Passphrase:", + "FIELD_HEADER_SFTP_PASSWORD" : "Password:", + "FIELD_HEADER_SFTP_PORT" : "Porta:", + "FIELD_HEADER_SFTP_PRIVATE_KEY" : "Chiave privata:", + "FIELD_HEADER_SFTP_PUBLIC_KEY" : "Chiave pubblica:", + "FIELD_HEADER_SFTP_ROOT_DIRECTORY" : "Directory radice del browser file:", + "FIELD_HEADER_SFTP_TIMEOUT" : "Timeout della connessione SFTP:", + "FIELD_HEADER_SFTP_USERNAME" : "Nome utente:", + "FIELD_HEADER_SWAP_RED_BLUE" : "Scambia componenti rosso/blu:", + "FIELD_HEADER_WOL_BROADCAST_ADDR" : "Indirizzo broadcast per pacchetto WoL:", + "FIELD_HEADER_WOL_MAC_ADDR" : "Indirizzo MAC dell'host remoto:", + "FIELD_HEADER_WOL_SEND_PACKET" : "Invia pacchetto WoL:", + "FIELD_HEADER_WOL_UDP_PORT" : "Porta UDP per pacchetto WoL:", + "FIELD_HEADER_WOL_WAIT_TIME" : "Tempo di attesa per l'avvio dell'host:", + + "FIELD_OPTION_COLOR_DEPTH_8" : "256 colori", + "FIELD_OPTION_COLOR_DEPTH_16" : "Colore basso (16 bit)", + "FIELD_OPTION_COLOR_DEPTH_24" : "Colore reale (24 bit)", + "FIELD_OPTION_COLOR_DEPTH_32" : "Colore reale (32 bit)", + "FIELD_OPTION_COLOR_DEPTH_EMPTY" : "", + + "FIELD_OPTION_COMPRESS_LEVEL_0" : "0", + "FIELD_OPTION_COMPRESS_LEVEL_1" : "1", + "FIELD_OPTION_COMPRESS_LEVEL_2" : "2", + "FIELD_OPTION_COMPRESS_LEVEL_3" : "3", + "FIELD_OPTION_COMPRESS_LEVEL_4" : "4", + "FIELD_OPTION_COMPRESS_LEVEL_5" : "5", + "FIELD_OPTION_COMPRESS_LEVEL_6" : "6", + "FIELD_OPTION_COMPRESS_LEVEL_7" : "7", + "FIELD_OPTION_COMPRESS_LEVEL_8" : "8", + "FIELD_OPTION_COMPRESS_LEVEL_9" : "9", + "FIELD_OPTION_COMPRESS_LEVEL_EMPTY" : "", "FIELD_OPTION_CURSOR_EMPTY" : "", "FIELD_OPTION_CURSOR_LOCAL" : "Locale", "FIELD_OPTION_CURSOR_REMOTE" : "Remoto", + "FIELD_OPTION_CLIPBOARD_ENCODING_CP1252" : "CP1252", + "FIELD_OPTION_CLIPBOARD_ENCODING_EMPTY" : "", + "FIELD_OPTION_CLIPBOARD_ENCODING_ISO8859_1" : "ISO 8859-1", + "FIELD_OPTION_CLIPBOARD_ENCODING_UTF_8" : "UTF-8", + "FIELD_OPTION_CLIPBOARD_ENCODING_UTF_16" : "UTF-16", + + "FIELD_OPTION_QUALITY_LEVEL_0" : "0", + "FIELD_OPTION_QUALITY_LEVEL_1" : "1", + "FIELD_OPTION_QUALITY_LEVEL_2" : "2", + "FIELD_OPTION_QUALITY_LEVEL_3" : "3", + "FIELD_OPTION_QUALITY_LEVEL_4" : "4", + "FIELD_OPTION_QUALITY_LEVEL_5" : "5", + "FIELD_OPTION_QUALITY_LEVEL_6" : "6", + "FIELD_OPTION_QUALITY_LEVEL_7" : "7", + "FIELD_OPTION_QUALITY_LEVEL_8" : "8", + "FIELD_OPTION_QUALITY_LEVEL_9" : "9", + "FIELD_OPTION_QUALITY_LEVEL_EMPTY" : "", + "NAME" : "VNC", "SECTION_HEADER_AUDIO" : "Audio", @@ -458,8 +990,9 @@ "SECTION_HEADER_CLIPBOARD" : "Appunti", "SECTION_HEADER_DISPLAY" : "Schermo", "SECTION_HEADER_NETWORK" : "Rete", - "SECTION_HEADER_REPEATER" : "VNC Repeater", - "SECTION_HEADER_SFTP" : "SFTP" + "SECTION_HEADER_REPEATER" : "Ripetitore VNC", + "SECTION_HEADER_SFTP" : "SFTP", + "SECTION_HEADER_WOL" : "Wake-on-LAN (WoL)" }, @@ -474,119 +1007,157 @@ "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", "ACTION_NEW_CONNECTION" : "Nuova Connessione", "ACTION_NEW_CONNECTION_GROUP" : "Nuovo Gruppo", + "ACTION_IMPORT" : "@:APP.ACTION_IMPORT", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", - "HELP_CONNECTIONS" : "Fai click o tap sulla connessione qui sotto per gestire quella connessione. In base al tuo livello di accesso, le connessioni possono essere create, eliminate, e le relative proprietà (protocol, hostname, port, etc.) possono essere cambiate.", + "HELP_CONNECTIONS" : "Fai click o tap sulla connessione qui sotto per gestire quella connessione. In base al tuo livello di accesso, le connessioni possono essere create, eliminate, e le relative proprietà (protocol, hostname, port, etc.) possono essere cambiate.", - "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", + "INFO_ACTIVE_USER_COUNT" : "@:APP.INFO_ACTIVE_USER_COUNT", - "SECTION_HEADER_CONNECTIONS" : "Connessioni" + "SECTION_HEADER_CONNECTIONS" : "Connessioni" }, "SETTINGS_CONNECTION_HISTORY" : { - "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "ACTION_SEARCH" : "@:APP.ACTION_SEARCH", + "ACTION_DOWNLOAD" : "@:APP.ACTION_DOWNLOAD", - "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "FILENAME_HISTORY_CSV" : "storico_connessioni.csv", + "HELP_CONNECTION_HISTORY" : "Questa sezione riporta la cronologia delle connessioni recenti e può essere ordinata cliccando sulle intestazioni di colonna. Per cercare un record specifico, inserisci la stringa di testo da filtrare e clicca su \"Ricerca\". Verranno elencati solo i record che corrispondono al filtro.", + "INFO_NO_HISTORY" : "Nessuna registrazione corrispondente trovata", - "INFO_CONNECTION_DURATION_UNKNOWN" : "--", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", - "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Nome della connessione", - "TABLE_HEADER_SESSION_STARTDATE" : "Ora di inizio", - "TABLE_HEADER_SESSION_DURATION" : "Durata", - "TABLE_HEADER_SESSION_USERNAME" : "Nome utente" + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + + "INFO_CONNECTION_DURATION_UNKNOWN" : "--", + + "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Nome della connessione", + "TABLE_HEADER_SESSION_STARTDATE" : "Ora di inizio", + "TABLE_HEADER_SESSION_DURATION" : "Durata", + "TABLE_HEADER_SESSION_USERNAME" : "Nome utente", + "TABLE_HEADER_SESSION_REMOTEHOST" : "Host remoto", + "TEXT_HISTORY_DURATION" : "@:APP.TEXT_HISTORY_DURATION" }, "SETTINGS_PREFERENCES" : { - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", - "ACTION_UPDATE_PASSWORD" : "@:APP.ACTION_UPDATE_PASSWORD", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_UPDATE_PASSWORD" : "@:APP.ACTION_UPDATE_PASSWORD", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", - "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + "ERROR_PASSWORD_BLANK" : "@:APP.ERROR_PASSWORD_BLANK", + "ERROR_PASSWORD_MISMATCH" : "@:APP.ERROR_PASSWORD_MISMATCH", + + "HELP_LOCALE" : "Le opzioni seguenti sono correlate alle impostazioni locali dell'utente e influenzeranno il modo in cui vengono visualizzate varie parti dell'interfaccia", + "FIELD_HEADER_LANGUAGE" : "Lingua dell'interfaccia:", + "FIELD_HEADER_TIMEZONE" : "Fuso orario:", + "FIELD_HEADER_PASSWORD" : "Password:", + "FIELD_HEADER_PASSWORD_OLD" : "Password Attuale:", + "FIELD_HEADER_PASSWORD_NEW" : "Nuova Password:", + "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Conferma Nuova Password:", + "FIELD_HEADER_USERNAME" : "Nome utente", - "FIELD_HEADER_LANGUAGE" : "Lingua dell'interfaccia:", - "FIELD_HEADER_PASSWORD" : "Password:", - "FIELD_HEADER_PASSWORD_OLD" : "Password Attuale:", - "FIELD_HEADER_PASSWORD_NEW" : "Nuova Password:", - "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Conferma Nuova Password:", - "FIELD_HEADER_USERNAME" : "Nome utente", + "SECTION_HEADER_APPEARANCE" : "Aspetto", + "HELP_APPEARANCE" : "Qui puoi abilitare o disabilitare la visualizzazione della sezione \"Connessioni recenti\" nella schermata iniziale e regolare il numero di connessioni recenti incluse.", + "FIELD_HEADER_SHOW_RECENT_CONNECTIONS" : "Visualizza connessioni recenti:", + "FIELD_HEADER_NUMBER_RECENT_CONNECTIONS" : "Numero di connessioni recenti da mostrare:", - "HELP_DEFAULT_INPUT_METHOD" : "Il metodo di input predefinito determina come gli eventi della tastiera vengono ricevuti da Guacamole. La modifica di questa impostazione potrebbe essere necessaria quando si utilizza un dispositivo mobile o quando si digita un IME. Questa impostazione può essere ignorata in base alla connessione all'interno del menu Guacamole.", - "HELP_DEFAULT_MOUSE_MODE" : "La modalità di emulazione del mouse predefinita determina come si comporterà il mouse remoto nelle nuove connessioni rispetto ai tocchi. Questa impostazione può essere ignorata in base alla connessione all'interno del menu Guacamole.", - "HELP_INPUT_METHOD_NONE" : "@:CLIENT.HELP_INPUT_METHOD_NONE", - "HELP_INPUT_METHOD_OSK" : "@:CLIENT.HELP_INPUT_METHOD_OSK", - "HELP_INPUT_METHOD_TEXT" : "@:CLIENT.HELP_INPUT_METHOD_TEXT", - "HELP_LANGUAGE" : "Seleziona una lingua diversa di seguito per cambiare la lingua di tutto il testo all'interno di Guacamole. Le scelte disponibili dipenderanno dalle lingue installate.", - "HELP_MOUSE_MODE_ABSOLUTE" : "@:CLIENT.HELP_MOUSE_MODE_ABSOLUTE", - "HELP_MOUSE_MODE_RELATIVE" : "@:CLIENT.HELP_MOUSE_MODE_RELATIVE", - "HELP_UPDATE_PASSWORD" : "Se desideri cambiare la tua password, inserisci la tua password attuale e sotto scrivi quella che desideri come nuova password, clicca \"Modifica Password\". La modifica avrà effetto immediato.", - - "INFO_PASSWORD_CHANGED" : "Password Modificata.", - - "NAME_INPUT_METHOD_NONE" : "@:CLIENT.NAME_INPUT_METHOD_NONE", - "NAME_INPUT_METHOD_OSK" : "@:CLIENT.NAME_INPUT_METHOD_OSK", - "NAME_INPUT_METHOD_TEXT" : "@:CLIENT.NAME_INPUT_METHOD_TEXT", - - "SECTION_HEADER_DEFAULT_INPUT_METHOD" : "Metodo di immissione predefinito", - "SECTION_HEADER_DEFAULT_MOUSE_MODE" : "Modalità di emulazione del mouse predefinita", - "SECTION_HEADER_UPDATE_PASSWORD" : "Modifica Password" + "HELP_DEFAULT_INPUT_METHOD" : "Il metodo di input predefinito determina come gli eventi della tastiera vengono ricevuti da Guacamole. La modifica di questa impostazione potrebbe essere necessaria quando si utilizza un dispositivo mobile o quando si digita un IME. Questa impostazione può essere ignorata in base alla connessione all'interno del menu Guacamole.", + "HELP_DEFAULT_MOUSE_MODE" : "La modalità di emulazione del mouse predefinita determina come si comporterà il mouse remoto nelle nuove connessioni rispetto ai tocchi. Questa impostazione può essere ignorata in base alla connessione all'interno del menu Guacamole.", + "HELP_INPUT_METHOD_NONE" : "@:CLIENT.HELP_INPUT_METHOD_NONE", + "HELP_INPUT_METHOD_OSK" : "@:CLIENT.HELP_INPUT_METHOD_OSK", + "HELP_INPUT_METHOD_TEXT" : "@:CLIENT.HELP_INPUT_METHOD_TEXT", + "HELP_LANGUAGE" : "Seleziona una lingua diversa di seguito per cambiare la lingua di tutto il testo all'interno di Guacamole. Le scelte disponibili dipenderanno dalle lingue installate.", + "HELP_MOUSE_MODE_ABSOLUTE" : "@:CLIENT.HELP_MOUSE_MODE_ABSOLUTE", + "HELP_MOUSE_MODE_RELATIVE" : "@:CLIENT.HELP_MOUSE_MODE_RELATIVE", + "HELP_UPDATE_PASSWORD" : "Se desideri cambiare la tua password, inserisci la tua password attuale e sotto scrivi quella che desideri come nuova password, clicca \"Modifica Password\". La modifica avrà effetto immediato.", + + "INFO_PASSWORD_CHANGED" : "Password Modificata.", + + "NAME_INPUT_METHOD_NONE" : "@:CLIENT.NAME_INPUT_METHOD_NONE", + "NAME_INPUT_METHOD_OSK" : "@:CLIENT.NAME_INPUT_METHOD_OSK", + "NAME_INPUT_METHOD_TEXT" : "@:CLIENT.NAME_INPUT_METHOD_TEXT", + + "SECTION_HEADER_DEFAULT_INPUT_METHOD" : "Metodo di immissione predefinito", + "SECTION_HEADER_DEFAULT_MOUSE_MODE" : "Modalità di emulazione del mouse predefinita", + "SECTION_HEADER_UPDATE_PASSWORD" : "Modifica Password" }, "SETTINGS_USERS" : { - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_NEW_USER" : "Nuovo utente", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_NEW_USER" : "Nuovo utente", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", - "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", - "HELP_USERS" : "Fare clic o toccare un utente di seguito per gestire quell'utente. A seconda del livello di accesso, gli utenti possono essere aggiunti ed eliminati e le loro password possono essere modificate.", + "HELP_USERS" : "Fare clic o toccare un utente di seguito per gestire quell'utente. A seconda del livello di accesso, gli utenti possono essere aggiunti ed eliminati e le loro password possono essere modificate.", - "SECTION_HEADER_USERS" : "Utenti", - - "TABLE_HEADER_USERNAME" : "Nome utente" + "SECTION_HEADER_USERS" : "Utenti", + "TABLE_HEADER_USERNAME" : "Nome utente", + "TABLE_HEADER_FULL_NAME" : "Nome completo", + "TABLE_HEADER_LAST_ACTIVE" : "Ultima connessione", + "TABLE_HEADER_ORGANIZATION" : "Organizzazione" + }, + "SETTINGS_USER_GROUPS" : { + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_NEW_USER_GROUP" : "Nuovo Gruppo", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FORMAT_DATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "HELP_USER_GROUPS" : "Fai clic o tocca un gruppo nell'elenco sottostante per gestirlo. A seconda del tuo livello di accesso, puoi aggiungere/eliminare gruppi e modificarne i membri e la composizione.", + "SECTION_HEADER_USER_GROUPS" : "Gruppi", + "TABLE_HEADER_USER_GROUP_NAME" : "Nome del Gruppo" + }, + "SETTINGS_SESSIONS" : { - "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", - "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", - "ACTION_DELETE" : "Termina Sessione", + "ACTION_ACKNOWLEDGE" : "@:APP.ACTION_ACKNOWLEDGE", + "ACTION_CANCEL" : "@:APP.ACTION_CANCEL", + "ACTION_DELETE" : "Termina Sessione", - "DIALOG_HEADER_CONFIRM_DELETE" : "Termina Sessione", - "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", + "DIALOG_HEADER_CONFIRM_DELETE" : "Termina Sessione", + "DIALOG_HEADER_ERROR" : "@:APP.DIALOG_HEADER_ERROR", - "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", + "FIELD_PLACEHOLDER_FILTER" : "@:APP.FIELD_PLACEHOLDER_FILTER", - "FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", + "FORMAT_STARTDATE" : "@:APP.FORMAT_DATE_TIME_PRECISE", - "HELP_SESSIONS" : "Questa pagina verrà popolata con connessioni attualmente attive. Le connessioni elencate e la possibilità di terminare tali connessioni dipende dal tuo livello di accesso. Se desideri terminare una o più sessioni, seleziona la casella accanto a quelle sessioni e fai clic su \"Termina sessione \". Terminando una sessione si disconnetterà immediatamente l'utente dalla connessione associata.", + "HELP_SESSIONS" : "Questa pagina verrà popolata con connessioni attualmente attive. Le connessioni elencate e la possibilità di terminare tali connessioni dipende dal tuo livello di accesso. Se desideri terminare una o più sessioni, seleziona la casella accanto a quelle sessioni e fai clic su \"Termina sessione \". Terminando una sessione si disconnetterà immediatamente l'utente dalla connessione associata.", - "INFO_NO_SESSIONS" : "Nessuna sessione attiva", + "INFO_NO_SESSIONS" : "Nessuna sessione attiva", - "SECTION_HEADER_SESSIONS" : "Sessioni Attive", + "SECTION_HEADER_SESSIONS" : "Sessioni Attive", - "TABLE_HEADER_SESSION_USERNAME" : "Nome utente", - "TABLE_HEADER_SESSION_STARTDATE" : "Attivo da", - "TABLE_HEADER_SESSION_REMOTEHOST" : "Host remoto", - "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Nome della connessione", + "TABLE_HEADER_SESSION_USERNAME" : "Nome utente", + "TABLE_HEADER_SESSION_STARTDATE" : "Attivo da", + "TABLE_HEADER_SESSION_REMOTEHOST" : "Host remoto", + "TABLE_HEADER_SESSION_CONNECTION_NAME" : "Nome della connessione", - "TEXT_CONFIRM_DELETE" : "Sei sicuro di voler terminare la sessione selezionata? L'utente che sta utilizzando questa sessione sarà immediatamente disconnesso." + "TEXT_CONFIRM_DELETE" : "Sei sicuro di voler terminare la sessione selezionata? L'utente che sta utilizzando questa sessione sarà immediatamente disconnesso." + + }, + "USER_ATTRIBUTES" : { + "FIELD_HEADER_GUAC_EMAIL_ADDRESS" : "Posta elettronica:", + "FIELD_HEADER_GUAC_FULL_NAME" : "Nome completo:", + "FIELD_HEADER_GUAC_ORGANIZATION" : "Organizzazione:", + "FIELD_HEADER_GUAC_ORGANIZATIONAL_ROLE" : "Ruolo:" }, "USER_MENU" : {