Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
3
extensions/guacamole-auth-duo/.gitignore
vendored
Normal file
3
extensions/guacamole-auth-duo/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
src/main/resources/generated/
|
||||
target/
|
||||
*~
|
2
extensions/guacamole-auth-duo/.ratignore
Normal file
2
extensions/guacamole-auth-duo/.ratignore
Normal file
@@ -0,0 +1,2 @@
|
||||
src/main/resources/lib/DuoWeb/**/*
|
||||
src/main/java/com/duosecurity/duoweb/**/*
|
142
extensions/guacamole-auth-duo/pom.xml
Normal file
142
extensions/guacamole-auth-duo/pom.xml
Normal file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-auth-duo</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.6.0</version>
|
||||
<name>guacamole-auth-duo</name>
|
||||
<url>http://guacamole.apache.org/</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>extensions</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<kotlin.version>1.9.25</kotlin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Guacamole Extension API -->
|
||||
<dependency>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-ext</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Guava - Utility Library -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Guice -->
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Java servlet API -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Duo SDK -->
|
||||
<dependency>
|
||||
<groupId>com.duosecurity</groupId>
|
||||
<artifactId>duo-universal-sdk</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Force the use of a consistent version of "okhttp" -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>${okhttp.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-common</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>${okhttp.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Force the use of a consistent version of Kotlin standard library common -->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-common</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Library for unified IPv4/6 parsing and validation -->
|
||||
<dependency>
|
||||
<groupId>com.github.seancfoley</groupId>
|
||||
<artifactId>ipaddress</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
53
extensions/guacamole-auth-duo/src/main/assembly/dist.xml
Normal file
53
extensions/guacamole-auth-duo/src/main/assembly/dist.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
|
||||
<id>dist</id>
|
||||
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
|
||||
|
||||
<!-- Output tar.gz -->
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
<!-- Include licenses and extension .jar -->
|
||||
<fileSets>
|
||||
|
||||
<!-- Include licenses -->
|
||||
<fileSet>
|
||||
<outputDirectory></outputDirectory>
|
||||
<directory>target/licenses</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include extension .jar -->
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
</fileSets>
|
||||
|
||||
</assembly>
|
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.duo;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
|
||||
/**
|
||||
* AuthenticationProvider implementation which uses Duo as an additional
|
||||
* authentication factor for users which have already been authenticated by
|
||||
* some other AuthenticationProvider.
|
||||
*/
|
||||
public class DuoAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* The unique identifier for this authentication provider. This is used in
|
||||
* various parts of the Guacamole client to distinguish this provider from
|
||||
* others, particularly when multiple authentication providers are used.
|
||||
*/
|
||||
public static String PROVIDER_IDENTIFER = "duo";
|
||||
|
||||
/**
|
||||
* Service for verifying the identity of users that Guacamole has otherwise
|
||||
* already authenticated.
|
||||
*/
|
||||
private final UserVerificationService verificationService;
|
||||
|
||||
/**
|
||||
* Session manager for storing/retrieving the state of a user's
|
||||
* authentication attempt while they are redirected to the Duo service.
|
||||
*/
|
||||
private final DuoAuthenticationSessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* Creates a new DuoAuthenticationProvider that verifies users
|
||||
* using the Duo authentication service
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a required property is missing, or an error occurs while parsing
|
||||
* a property.
|
||||
*/
|
||||
public DuoAuthenticationProvider() throws GuacamoleException {
|
||||
|
||||
// Set up Guice injector.
|
||||
Injector injector = Guice.createInjector(
|
||||
new DuoAuthenticationProviderModule(this)
|
||||
);
|
||||
|
||||
sessionManager = injector.getInstance(DuoAuthenticationSessionManager.class);
|
||||
verificationService = injector.getInstance(UserVerificationService.class);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return PROVIDER_IDENTIFER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Credentials updateCredentials(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Ignore requests with no corresponding authentication session ID, as
|
||||
// there are no credentials to reconstitute if the user has not yet
|
||||
// attempted to authenticate
|
||||
String duoState = credentials.getParameter(UserVerificationService.DUO_STATE_PARAMETER_NAME);
|
||||
if (duoState == null)
|
||||
return credentials;
|
||||
|
||||
// Ignore requests with invalid/expired authentication session IDs
|
||||
DuoAuthenticationSession session = sessionManager.resume(duoState);
|
||||
if (session == null)
|
||||
return credentials;
|
||||
|
||||
// Reconstitute the originally-provided credentials from the users
|
||||
// authentication attempt prior to being redirected to Duo
|
||||
Credentials previousCredentials = session.getCredentials();
|
||||
previousCredentials.setRequestDetails(credentials.getRequestDetails());
|
||||
return previousCredentials;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext getUserContext(AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Verify user against Duo service
|
||||
verificationService.verifyAuthenticatedUser(authenticatedUser);
|
||||
|
||||
// User has been verified, and authentication should be allowed to
|
||||
// continue
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
sessionManager.shutdown();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.duo;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.auth.duo.conf.ConfigurationService;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
|
||||
/**
|
||||
* Guice module which configures Duo-specific injections.
|
||||
*/
|
||||
public class DuoAuthenticationProviderModule extends AbstractModule {
|
||||
|
||||
/**
|
||||
* Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* A reference to the DuoAuthenticationProvider on behalf of which this
|
||||
* module has configured injection.
|
||||
*/
|
||||
private final AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* Creates a new Duo authentication provider module which configures
|
||||
* injection for the DuoAuthenticationProvider.
|
||||
*
|
||||
* @param authProvider
|
||||
* The AuthenticationProvider for which injection is being configured.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the Guacamole server
|
||||
* environment.
|
||||
*/
|
||||
public DuoAuthenticationProviderModule(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 Duo-specific services
|
||||
bind(ConfigurationService.class);
|
||||
bind(UserVerificationService.class);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.duo;
|
||||
|
||||
import org.apache.guacamole.net.auth.AuthenticationSession;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
|
||||
/**
|
||||
* Representation of an in-progress Duo authentication attempt.
|
||||
*/
|
||||
public class DuoAuthenticationSession extends AuthenticationSession {
|
||||
|
||||
/**
|
||||
* The credentials that the user originally provided to Guacamole prior to
|
||||
* being redirected to the Duo service.
|
||||
*/
|
||||
private final Credentials credentials;
|
||||
|
||||
/**
|
||||
* Creates a new AuthenticationSession representing an in-progress Duo
|
||||
* authentication attempt.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials that the user originally provided to Guacamole prior
|
||||
* to being redirected to the Duo service.
|
||||
*
|
||||
* @param expires
|
||||
* The number of milliseconds that may elapse before this session must
|
||||
* be considered invalid.
|
||||
*/
|
||||
public DuoAuthenticationSession(Credentials credentials, long expires) {
|
||||
super(expires);
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the credentials that the user originally provided to Guacamole
|
||||
* prior to being redirected to the Duo service.
|
||||
*
|
||||
* @return
|
||||
* The credentials that the user originally provided to Guacamole prior
|
||||
* to being redirected to the Duo service.
|
||||
*/
|
||||
public Credentials getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.auth.duo;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.guacamole.net.auth.AuthenticationSessionManager;
|
||||
|
||||
/**
|
||||
* Manager service that temporarily stores authentication attempts while
|
||||
* the Duo authentication flow is underway.
|
||||
*/
|
||||
@Singleton
|
||||
public class DuoAuthenticationSessionManager
|
||||
extends AuthenticationSessionManager<DuoAuthenticationSession> {
|
||||
|
||||
// Intentionally empty (the default functions inherited from the
|
||||
// AuthenticationSessionManager base class are sufficient for our needs)
|
||||
|
||||
}
|
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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.duo;
|
||||
|
||||
import com.duosecurity.Client;
|
||||
import com.duosecurity.exception.DuoException;
|
||||
import com.duosecurity.model.Token;
|
||||
import com.google.inject.Inject;
|
||||
import inet.ipaddr.IPAddress;
|
||||
import inet.ipaddr.IPAddressString;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.auth.duo.conf.ConfigurationService;
|
||||
import org.apache.guacamole.form.RedirectField;
|
||||
import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.apache.guacamole.properties.IPAddressListProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service for verifying the identity of a user against Duo.
|
||||
*/
|
||||
public class UserVerificationService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
|
||||
|
||||
/**
|
||||
* The name of the HTTP parameter that Duo will use to communicate the
|
||||
* result of the user's attempt to authenticate with their service. This
|
||||
* parameter is provided in the GET request received when Duo redirects the
|
||||
* user back to Guacamole.
|
||||
*/
|
||||
public static final String DUO_CODE_PARAMETER_NAME = "duo_code";
|
||||
|
||||
/**
|
||||
* The name of the HTTP parameter that we will be using to hold the opaque
|
||||
* authentication session ID. This session ID is transmitted to Duo during
|
||||
* the initial redirect and received back from Duo via this parameter in
|
||||
* the GET request received when Duo redirects the user back to Guacamole.
|
||||
* The session ID is ultimately used to reconstitute the original
|
||||
* credentials received from the user by Guacamole such that parameter
|
||||
* tokens like GUAC_USERNAME and GUAC_PASSWORD can continue to work as
|
||||
* expected.
|
||||
*/
|
||||
public static final String DUO_STATE_PARAMETER_NAME = "state";
|
||||
|
||||
/**
|
||||
* The value that will be returned in the token if Duo authentication
|
||||
* was successful.
|
||||
*/
|
||||
private static final String DUO_TOKEN_SUCCESS_VALUE = "allow";
|
||||
|
||||
/**
|
||||
* Session manager for storing/retrieving the state of a user's
|
||||
* authentication attempt while they are redirected to the Duo service.
|
||||
*/
|
||||
@Inject
|
||||
private DuoAuthenticationSessionManager sessionManager;
|
||||
|
||||
/**
|
||||
* Service for retrieving Duo configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Verifies the identity of the given user via the Duo multi-factor
|
||||
* authentication service. If a signed response from Duo has not already
|
||||
* been provided, a signed response from Duo is requested in the
|
||||
* form of additional expected credentials. Any provided signed response
|
||||
* is cryptographically verified. If no signed response is present, or the
|
||||
* signed response is invalid, an exception is thrown.
|
||||
*
|
||||
* @param authenticatedUser
|
||||
* The user whose identity should be verified against Duo.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If required Duo-specific configuration options are missing or
|
||||
* malformed, or if the user's identity cannot be verified.
|
||||
*/
|
||||
public void verifyAuthenticatedUser(AuthenticatedUser authenticatedUser)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull the original HTTP request used to authenticate
|
||||
Credentials credentials = authenticatedUser.getCredentials();
|
||||
IPAddress clientAddr = new IPAddressString(credentials.getRemoteAddress()).getAddress();
|
||||
|
||||
// Ignore anonymous users
|
||||
String username = authenticatedUser.getIdentifier();
|
||||
if (username == null || username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
|
||||
return;
|
||||
|
||||
// Pull address lists to check from configuration. Note that the enforce
|
||||
// list will override the bypass list, which means that, if the client
|
||||
// address happens to be in both lists, Duo MFA will be enforced.
|
||||
List<IPAddress> bypassAddresses = confService.getBypassHosts();
|
||||
List<IPAddress> enforceAddresses = confService.getEnforceHosts();
|
||||
|
||||
// Check if the bypass list contains the client address, and set the
|
||||
// enforce flag to the opposite.
|
||||
boolean enforceHost = !(IPAddressListProperty.addressListContains(bypassAddresses, clientAddr));
|
||||
|
||||
// Only continue processing if the list is not empty
|
||||
if (!enforceAddresses.isEmpty()) {
|
||||
|
||||
// If client address is not available or invalid, MFA will
|
||||
// be enforced.
|
||||
if (clientAddr == null || !clientAddr.isIPAddress())
|
||||
enforceHost = true;
|
||||
|
||||
// Check the enforce list for the client address and set enforcement flag.
|
||||
else
|
||||
enforceHost = IPAddressListProperty.addressListContains(enforceAddresses, clientAddr);
|
||||
}
|
||||
|
||||
// If the enforce flag is not true, bypass Duo MFA.
|
||||
if (!enforceHost)
|
||||
return;
|
||||
|
||||
// Obtain a Duo client for redirecting the user to the Duo service and
|
||||
// verifying any received authentication code
|
||||
Client duoClient;
|
||||
try {
|
||||
duoClient = new Client.Builder(
|
||||
confService.getClientId(),
|
||||
confService.getClientSecret(),
|
||||
confService.getAPIHostname(),
|
||||
confService.getRedirectUri().toString())
|
||||
.build();
|
||||
}
|
||||
catch (DuoException e) {
|
||||
throw new GuacamoleServerException("Client for communicating with "
|
||||
+ "the Duo authentication service could not be created.", e);
|
||||
}
|
||||
|
||||
// Verify that the Duo service is healthy and available
|
||||
try {
|
||||
duoClient.healthCheck();
|
||||
}
|
||||
catch (DuoException e) {
|
||||
throw new GuacamoleServerException("Duo authentication service is "
|
||||
+ "not currently available (failed health check).", e);
|
||||
}
|
||||
|
||||
// Retrieve signed Duo authentication code and session state from the
|
||||
// request (these will be absent if this is an initial authentication
|
||||
// attempt and not a redirect back from Duo)
|
||||
String duoCode = credentials.getParameter(DUO_CODE_PARAMETER_NAME);
|
||||
String duoState = credentials.getParameter(DUO_STATE_PARAMETER_NAME);
|
||||
|
||||
// Redirect to Duo to obtain an authentication code if that redirect
|
||||
// has not yet occurred
|
||||
if (duoCode != null && duoState != null) {
|
||||
|
||||
// Validate that the user has successfully verified their identify with
|
||||
// the Duo service
|
||||
try {
|
||||
|
||||
// Note unexpected behavior (Duo is expected to always return
|
||||
// a token)
|
||||
Token token = duoClient.exchangeAuthorizationCodeFor2FAResult(duoCode, username);
|
||||
if (token == null) {
|
||||
logger.warn("Duo did not return an authentication result "
|
||||
+ "at all for the authentication attempt by user "
|
||||
+ "\"{}\". This is unexpected behavior and may be "
|
||||
+ "a bug in the Duo service or the Duo SDK. "
|
||||
+ "Guacamole will attempt to automatically work "
|
||||
+ "around the issue by making a fresh Duo "
|
||||
+ "authentication request.", username);
|
||||
}
|
||||
|
||||
// Warn if Duo explicitly denies authentication
|
||||
else if (token.getAuth_result() == null || !DUO_TOKEN_SUCCESS_VALUE.equals(token.getAuth_result().getStatus())) {
|
||||
logger.warn("Duo did not return an explicitly successful "
|
||||
+ "authentication result for the authentication "
|
||||
+ "attempt by user \"{}\". The user will now be "
|
||||
+ "redirected back to the Duo service to reattempt"
|
||||
+ "authentication.", username);
|
||||
}
|
||||
|
||||
// Allow user to continue authenticating with Guacamole only if
|
||||
// Duo has validated their identity
|
||||
else
|
||||
return;
|
||||
|
||||
}
|
||||
catch (DuoException e) {
|
||||
logger.debug("The Duo client failed internally while "
|
||||
+ "attempting to validate the identity of user "
|
||||
+ "\"{}\". This is commonly caused by stale query "
|
||||
+ "parameters from an older Duo request remaining "
|
||||
+ "present in the Guacamole URL. The user will now be "
|
||||
+ "redirected back to the Duo service to reattempt "
|
||||
+ "authentication.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Store received credentials for later retrieval leveraging Duo's
|
||||
// opaque session state identifier (we need to maintain these
|
||||
// credentials so that things like the GUAC_USERNAME and
|
||||
// GUAC_PASSWORD tokens continue to work as expected despite the
|
||||
// redirect to/from the external Duo service)
|
||||
duoState = duoClient.generateState();
|
||||
long expiresAfter = TimeUnit.MINUTES.toMillis(confService.getAuthenticationTimeout());
|
||||
sessionManager.defer(new DuoAuthenticationSession(credentials, expiresAfter), duoState);
|
||||
|
||||
// Obtain authentication URL from Duo client
|
||||
String duoAuthUrlString;
|
||||
try {
|
||||
duoAuthUrlString = duoClient.createAuthUrl(username, duoState);
|
||||
}
|
||||
catch (DuoException e) {
|
||||
throw new GuacamoleServerException("Duo client failed to "
|
||||
+ "generate the authentication URL necessary to "
|
||||
+ "redirect the authenticating user to the Duo "
|
||||
+ "service.", e);
|
||||
}
|
||||
|
||||
// Parse and validate URL obtained from Duo client
|
||||
URI duoAuthUrl;
|
||||
try {
|
||||
duoAuthUrl = new URI(duoAuthUrlString);
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new GuacamoleServerException("Authentication URL "
|
||||
+ "generated by the Duo client is not actually a "
|
||||
+ "valid URL and cannot be used to redirect the "
|
||||
+ "authenticating user to the Duo service.", e);
|
||||
}
|
||||
|
||||
// Request that user be redirected to the Duo service to obtain
|
||||
// a Duo authentication code
|
||||
throw new TranslatableGuacamoleInsufficientCredentialsException(
|
||||
"Verification using Duo is required before authentication "
|
||||
+ "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
|
||||
new CredentialsInfo(Collections.singletonList(
|
||||
new RedirectField(
|
||||
DUO_CODE_PARAMETER_NAME, duoAuthUrl,
|
||||
new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.duo.conf;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import inet.ipaddr.IPAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.IPAddressListProperty;
|
||||
import org.apache.guacamole.properties.StringGuacamoleProperty;
|
||||
import org.apache.guacamole.properties.URIGuacamoleProperty;
|
||||
|
||||
/**
|
||||
* Service for retrieving configuration information regarding the Duo
|
||||
* authentication extension.
|
||||
*/
|
||||
public class ConfigurationService {
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
@Inject
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* The property within guacamole.properties which defines the hostname
|
||||
* of the Duo API endpoint to be used to verify user identities. This will
|
||||
* usually be in the form "api-XXXXXXXX.duosecurity.com", where "XXXXXXXX"
|
||||
* is some arbitrary alphanumeric value assigned by Duo and specific to
|
||||
* your organization.
|
||||
*/
|
||||
private static final StringGuacamoleProperty DUO_API_HOSTNAME =
|
||||
new StringGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-api-hostname"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The property within guacamole.properties which defines the client id
|
||||
* received from Duo for verifying Guacamole users. This value MUST be
|
||||
* exactly 20 characters.
|
||||
*/
|
||||
private static final StringGuacamoleProperty DUO_CLIENT_ID =
|
||||
new StringGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-client-id"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The property within guacamole.properties which defines the secret key
|
||||
* received from Duo for verifying Guacamole users. This value MUST be
|
||||
* exactly 40 characters.
|
||||
*/
|
||||
private static final StringGuacamoleProperty DUO_CLIENT_SECRET =
|
||||
new StringGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-client-secret"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The property within guacamole.properties which defines the redirect URI
|
||||
* that Duo will call after the second factor has been completed. This
|
||||
* should be the URI used to access Guacamole.
|
||||
*/
|
||||
private static final URIGuacamoleProperty DUO_REDIRECT_URI =
|
||||
new URIGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-redirect-uri"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The property that configures the timeout, in minutes, of in-progress
|
||||
* Duo authentication attempts. Authentication attempts that take longer
|
||||
* than this period of time will be invalidated.
|
||||
*/
|
||||
private static final IntegerGuacamoleProperty DUO_AUTH_TIMEOUT =
|
||||
new IntegerGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-auth-timeout"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The optional property that contains a comma-separated list of IP addresses
|
||||
* or CIDRs for which the MFA requirement should be bypassed. If the Duo
|
||||
* extension is installed, any/all users authenticating from clients that
|
||||
* match this list will be able to successfully log in without fulfilling
|
||||
* the MFA requirement. If this option is omitted or is empty, and the
|
||||
* Duo module is installed, all users from all hosts will have Duo MFA
|
||||
* enforced.
|
||||
*/
|
||||
private static final IPAddressListProperty DUO_BYPASS_HOSTS =
|
||||
new IPAddressListProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-bypass-hosts"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The optional property that contains a comma-separated list of IP addresses
|
||||
* or CIDRs for which the MFA requirement should be explicitly enforced. If
|
||||
* the Duo module is enabled and this property is specified, users that log
|
||||
* in from hosts that match the items in this list will have Duo MFA required,
|
||||
* and all users from hosts that do not match this list will be able to log
|
||||
* in without the MFA requirement. If this option is missing or empty and
|
||||
* the Duo module is installed, MFA will be enforced for all users.
|
||||
*/
|
||||
private static final IPAddressListProperty DUO_ENFORCE_HOSTS =
|
||||
new IPAddressListProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "duo-enforce-hosts"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the hostname of the Duo API endpoint to be used to verify user
|
||||
* identities, as defined in guacamole.properties by the "duo-api-hostname"
|
||||
* property. This will usually be in the form
|
||||
* "api-XXXXXXXX.duosecurity.com", where "XXXXXXXX" is some arbitrary
|
||||
* alphanumeric value assigned by Duo and specific to your organization.
|
||||
*
|
||||
* @return
|
||||
* The hostname of the Duo API endpoint to be used to verify user
|
||||
* identities.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the associated property within guacamole.properties is missing.
|
||||
*/
|
||||
public String getAPIHostname() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(DUO_API_HOSTNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Duo client id received from Duo for verifying Guacamole
|
||||
* users, as defined in guacamole.properties by the "duo-client-id"
|
||||
* property. This value MUST be exactly 20 characters.
|
||||
*
|
||||
* @return
|
||||
* The client id received from Duo for verifying Guacamole users.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the associated property within guacamole.properties is missing.
|
||||
*/
|
||||
public String getClientId() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(DUO_CLIENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client secret received from Duo for verifying Guacamole users,
|
||||
* as defined in guacamole.properties by the "duo-client-secret" property.
|
||||
* This value MUST be exactly 20 characters.
|
||||
*
|
||||
* @return
|
||||
* The client secret received from Duo for verifying Guacamole users.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the associated property within guacamole.properties is missing.
|
||||
*/
|
||||
public String getClientSecret() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(DUO_CLIENT_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the callback URI that will be called by Duo after authentication
|
||||
* with Duo has been completed. This should be the URI to return the user
|
||||
* to the Guacamole interface, and will be a full URI.
|
||||
*
|
||||
* @return
|
||||
* The URL for Duo to use to callback to the Guacamole interface after
|
||||
* authentication has been completed.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be read, or if the property is not
|
||||
* defined.
|
||||
*/
|
||||
public URI getRedirectUri() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(DUO_REDIRECT_URI);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the maximum amount of time to allow for an in-progress Duo
|
||||
* authentication attempt to be completed, in minutes. A user that takes
|
||||
* longer than this amount of time to complete authentication with Duo
|
||||
* will need to try again.
|
||||
*
|
||||
* @return
|
||||
* The maximum amount of time to allow for an in-progress Duo
|
||||
* authentication attempt to be completed, in minutes.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the authentication timeout cannot be parsed.
|
||||
*/
|
||||
public int getAuthenticationTimeout() throws GuacamoleException {
|
||||
return environment.getProperty(DUO_AUTH_TIMEOUT, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IP addresses and subnets defined in guacamole.properties
|
||||
* for which Duo MFA should _not_ be enforced. Users logging in from hosts
|
||||
* contained in this list will be logged in without the MFA requirement.
|
||||
*
|
||||
* @return
|
||||
* A list of IP addresses and subnets for which Duo MFA should not be
|
||||
* enforced.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if an invalid IP address
|
||||
* or subnet is specified.
|
||||
*/
|
||||
public List<IPAddress> getBypassHosts() throws GuacamoleException {
|
||||
return environment.getProperty(DUO_BYPASS_HOSTS, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of IP addresses and subnets defined in guacamole.properties
|
||||
* for which Duo MFA should explicitly be enforced, while logins from all
|
||||
* other hosts should not enforce MFA. Users logging in from hosts
|
||||
* contained in this list will be required to complete the Duo MFA authentication,
|
||||
* while users from all other hosts will be logged in without the MFA requirement.
|
||||
*
|
||||
* @return
|
||||
* A list of IP addresses and subnets for which Duo MFA should be
|
||||
* explicitly enforced.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if an invalid IP address
|
||||
* or subnet is specified.
|
||||
*/
|
||||
public List<IPAddress> getEnforceHosts() throws GuacamoleException {
|
||||
return environment.getProperty(DUO_ENFORCE_HOSTS, Collections.emptyList());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "1.6.0",
|
||||
|
||||
"name" : "Duo TFA Authentication Backend",
|
||||
"namespace" : "duo",
|
||||
|
||||
"authProviders" : [
|
||||
"org.apache.guacamole.auth.duo.DuoAuthenticationProvider"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/ca.json",
|
||||
"translations/de.json",
|
||||
"translations/en.json",
|
||||
"translations/fr.json",
|
||||
"translations/ja.json",
|
||||
"translations/ko.json",
|
||||
"translations/pl.json",
|
||||
"translations/pt.json",
|
||||
"translations/ru.json",
|
||||
"translations/zh.json"
|
||||
]
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "El codi de validació duo és incorrecte.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Si us plau, autentiqueu amb Duo per continuar."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo Validierungscode nicht korrekt.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Bitte melden Sie sich mit Duo an, um fortzufahren."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo validation code incorrect.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Please authenticate with Duo to continue.",
|
||||
"INFO_DUO_REDIRECT_PENDING" : "Please wait, redirecting to Duo..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Code de validation Duo incorrect.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Veuillez vous authentifier avec Duo pour continuer.",
|
||||
"INFO_DUO_REDIRECT_PENDING" : "Veuillez patienter, redirection vers Duo..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Codice di convalida Duo errato.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Si prega di autenticarsi con Duo per continuare."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duoの認証コードが間違っています。",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Duoで認証してください。",
|
||||
"INFO_DUO_REDIRECT_PENDING" : "Duoへリダイレクトしています。"
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo 유효성 검사 코드가 잘못되었습니다.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "계속하려면 Duo로 인증하세요."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Nieprawidłowy kod weryfikacyjny Duo.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Aby kontynuować uwierzytelnij się w Duo."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Código de validação Duo incorreto.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Por favor autentique com Duo para continuar."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Бэкенд Duo TFA"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Неверный код валидации Duo.",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "Пожалуйста, аутентифицируйтесь в Duo для продолжения."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_DUO" : {
|
||||
"NAME" : "Duo TFA后端"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_GUAC_DUO_SIGNED_RESPONSE" : "",
|
||||
"INFO_DUO_VALIDATION_CODE_INCORRECT" : "Duo验证码不正确。",
|
||||
"INFO_DUO_AUTH_REQUIRED" : "请先使用Duo进行身份验证。"
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user