pp7En commented on code in PR #984:
URL: https://github.com/apache/guacamole-client/pull/984#discussion_r1632174834


##########
extensions/guacamole-auth-nextcloud/src/main/java/org/apache/guacamole/auth/nextcloud/NextcloudJwtAuthenticationProvider.java:
##########
@@ -0,0 +1,249 @@
+/*
+ * 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.nextcloud;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.Date;
+import javax.servlet.http.HttpServletRequest;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Allows a pre-check of users with encrypted Nextcloud JWT data blocks.
+ * The username in the JWT will be compared with a list in 
guacamole.properties.
+ * The JWT will be verified with the public key. If the JWT is valid, the login
+ * page will be loaded. If the JWT is missing or invalid, an exception message
+ * will be displayed.
+ */
+public class NextcloudJwtAuthenticationProvider extends 
AbstractAuthenticationProvider {
+
+    /**
+     * The duration in minutes for which a token remains valid.
+     *
+     * This short validity period increases security, as the time window for 
potential misuse,
+     * e.g. by stolen tokens, is limited. Nextcloud always generates a new 
valid token when the
+     * Guacamole login screen will be open through the Nextcloud plugin 
“External sites”.
+     */
+    private static final int MINUTES_TOKEN_VALID = 1;
+
+    /**
+     * Injector which will manage the object graph of this authentication
+     * provider.
+     */
+    private final Injector injector;
+
+    /**
+     * The configuration service for this module.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = 
LoggerFactory.getLogger(NextcloudJwtAuthenticationProvider.class);
+
+    /**
+     * Creates a new NextcloudJwtAuthenticationProvider that authenticates 
user.
+     *
+     * @throws GuacamoleException
+     *     If a required property is missing, or an error occurs while parsing
+     *     a property.
+     */
+    public NextcloudJwtAuthenticationProvider() throws GuacamoleException {
+
+        // Set up Guice injector.
+        injector = Guice.createInjector(new 
NextcloudJwtAuthenticationProviderModule(this));
+
+    }
+
+    @Override
+    public String getIdentifier() {
+        return "nextcloud";
+    }
+
+    @Override
+    public AuthenticatedUser authenticateUser(Credentials credentials) throws 
GuacamoleException {
+
+        HttpServletRequest request = credentials.getRequest();
+
+        String token = request.getParameter(confService.getTokenName());
+        String ipaddr = request.getRemoteAddr();
+
+        boolean localAddr = this.validIpAddress(ipaddr);
+        if (localAddr) {
+            logger.info("Request from local address {}", ipaddr);
+            return null;
+        }
+
+        if (token == null) {
+            throw new GuacamoleException("Missing token.");
+        }
+
+        try {
+            boolean valid = this.isValidJWT(token);
+            if (!valid) {
+                throw new GuacamoleException("Token expired.");
+            }
+            logger.info("Token valid.");
+        } catch (final GuacamoleException ex) {
+            logger.error("Token validation failed.", ex);
+            throw new GuacamoleException(ex.getMessage());
+        }
+        return null;
+
+    }
+
+    /**
+     * Validates the provided JSON Web Token (JWT).
+     *
+     * This method decodes the public key from a base64 encoded string, 
verifies the JWT using
+     * the ECDSA256 algorithm, and checks the token's validity period and user 
permissions.
+     *
+     * @param token
+     *     The JWT token to validate.
+     * @return {@code true}
+     *     If the token is valid and the user is allowed, {@code false} 
otherwise.
+     * @throws GuacamoleException
+     *     If the user is not allowed or the token is expired.
+     * @throws JWTVerificationException
+     *     If the token verification fails.
+     * @throws NoSuchAlgorithmException
+     *     If the algorithm for key generation is not available.
+     * @throws InvalidKeySpecException
+     *     If the key specification is invalid.
+     */
+    private boolean isValidJWT(final String token) throws GuacamoleException {
+        try {
+            // Decode the Base64 encoded public key from configuration and 
generate the ECPublicKey object.
+            byte[] keyBytes = 
Base64.getDecoder().decode(confService.getPublicKey());
+            KeyFactory keyFactory = KeyFactory.getInstance("EC");
+            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+            ECPublicKey publicKey = (ECPublicKey) 
keyFactory.generatePublic(keySpec);
+
+            // Create a JWT verifier instance with the provided public key, 
verify the token and decode the content.
+            JWTVerifier verifier = 
JWT.require(Algorithm.ECDSA256(publicKey)).build();
+            DecodedJWT decodedJWT = verifier.verify(token);
+
+            // Check if the user extracted from the token's payload is allowed 
to open Guacamole login page.
+            boolean isUserAllowed = 
this.isUserAllowed(decodedJWT.getPayload());
+            if (!isUserAllowed) {
+                throw new GuacamoleException("User not allowed.");
+            }
+
+            // Validate the token's expiration by comparing the current date 
with the token's expiration date,
+            // ensuring it falls within the acceptable validity duration 
defined by MINUTES_TOKEN_VALID.
+            Date currentDate = new Date();
+            Date maxValidDate = new Date(currentDate.getTime() - 
(MINUTES_TOKEN_VALID * 60 * 1000));
+            boolean isValidToken = 
decodedJWT.getExpiresAt().after(maxValidDate);
+            if (!isValidToken) {
+                throw new GuacamoleException("Token expired.");
+            }
+
+            return true;
+        } catch (final JWTVerificationException | NoSuchAlgorithmException | 
InvalidKeySpecException ex) {
+            logger.error("Token validation failed.", ex);
+            return false;
+        }

Review Comment:
   Oh yes, `GuacamoleSecurityException` is much better. Thanks for the hint 
:+1: 



##########
extensions/guacamole-auth-nextcloud/src/main/java/org/apache/guacamole/auth/nextcloud/NextcloudJwtAuthenticationProvider.java:
##########
@@ -0,0 +1,249 @@
+/*
+ * 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.nextcloud;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+import java.util.Date;
+import javax.servlet.http.HttpServletRequest;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Allows a pre-check of users with encrypted Nextcloud JWT data blocks.
+ * The username in the JWT will be compared with a list in 
guacamole.properties.
+ * The JWT will be verified with the public key. If the JWT is valid, the login
+ * page will be loaded. If the JWT is missing or invalid, an exception message
+ * will be displayed.
+ */
+public class NextcloudJwtAuthenticationProvider extends 
AbstractAuthenticationProvider {
+
+    /**
+     * The duration in minutes for which a token remains valid.
+     *
+     * This short validity period increases security, as the time window for 
potential misuse,
+     * e.g. by stolen tokens, is limited. Nextcloud always generates a new 
valid token when the
+     * Guacamole login screen will be open through the Nextcloud plugin 
“External sites”.
+     */
+    private static final int MINUTES_TOKEN_VALID = 1;
+
+    /**
+     * Injector which will manage the object graph of this authentication
+     * provider.
+     */
+    private final Injector injector;
+
+    /**
+     * The configuration service for this module.
+     */
+    @Inject
+    private ConfigurationService confService;
+
+    /**
+     * Logger for this class.
+     */
+    private static final Logger logger = 
LoggerFactory.getLogger(NextcloudJwtAuthenticationProvider.class);
+
+    /**
+     * Creates a new NextcloudJwtAuthenticationProvider that authenticates 
user.
+     *
+     * @throws GuacamoleException
+     *     If a required property is missing, or an error occurs while parsing
+     *     a property.
+     */
+    public NextcloudJwtAuthenticationProvider() throws GuacamoleException {
+
+        // Set up Guice injector.
+        injector = Guice.createInjector(new 
NextcloudJwtAuthenticationProviderModule(this));
+
+    }
+
+    @Override
+    public String getIdentifier() {
+        return "nextcloud";
+    }
+
+    @Override
+    public AuthenticatedUser authenticateUser(Credentials credentials) throws 
GuacamoleException {
+
+        HttpServletRequest request = credentials.getRequest();
+
+        String token = request.getParameter(confService.getTokenName());
+        String ipaddr = request.getRemoteAddr();
+
+        boolean localAddr = this.validIpAddress(ipaddr);
+        if (localAddr) {
+            logger.info("Request from local address {}", ipaddr);
+            return null;
+        }
+
+        if (token == null) {
+            throw new GuacamoleException("Missing token.");
+        }
+
+        try {
+            boolean valid = this.isValidJWT(token);
+            if (!valid) {
+                throw new GuacamoleException("Token expired.");
+            }
+            logger.info("Token valid.");
+        } catch (final GuacamoleException ex) {
+            logger.error("Token validation failed.", ex);
+            throw new GuacamoleException(ex.getMessage());
+        }
+        return null;
+
+    }
+
+    /**
+     * Validates the provided JSON Web Token (JWT).
+     *
+     * This method decodes the public key from a base64 encoded string, 
verifies the JWT using
+     * the ECDSA256 algorithm, and checks the token's validity period and user 
permissions.
+     *
+     * @param token
+     *     The JWT token to validate.
+     * @return {@code true}
+     *     If the token is valid and the user is allowed, {@code false} 
otherwise.
+     * @throws GuacamoleException
+     *     If the user is not allowed or the token is expired.
+     * @throws JWTVerificationException
+     *     If the token verification fails.
+     * @throws NoSuchAlgorithmException
+     *     If the algorithm for key generation is not available.
+     * @throws InvalidKeySpecException
+     *     If the key specification is invalid.
+     */
+    private boolean isValidJWT(final String token) throws GuacamoleException {
+        try {
+            // Decode the Base64 encoded public key from configuration and 
generate the ECPublicKey object.
+            byte[] keyBytes = 
Base64.getDecoder().decode(confService.getPublicKey());
+            KeyFactory keyFactory = KeyFactory.getInstance("EC");
+            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
+            ECPublicKey publicKey = (ECPublicKey) 
keyFactory.generatePublic(keySpec);
+
+            // Create a JWT verifier instance with the provided public key, 
verify the token and decode the content.
+            JWTVerifier verifier = 
JWT.require(Algorithm.ECDSA256(publicKey)).build();
+            DecodedJWT decodedJWT = verifier.verify(token);
+
+            // Check if the user extracted from the token's payload is allowed 
to open Guacamole login page.
+            boolean isUserAllowed = 
this.isUserAllowed(decodedJWT.getPayload());
+            if (!isUserAllowed) {
+                throw new GuacamoleException("User not allowed.");
+            }
+
+            // Validate the token's expiration by comparing the current date 
with the token's expiration date,
+            // ensuring it falls within the acceptable validity duration 
defined by MINUTES_TOKEN_VALID.
+            Date currentDate = new Date();
+            Date maxValidDate = new Date(currentDate.getTime() - 
(MINUTES_TOKEN_VALID * 60 * 1000));
+            boolean isValidToken = 
decodedJWT.getExpiresAt().after(maxValidDate);
+            if (!isValidToken) {
+                throw new GuacamoleException("Token expired.");

Review Comment:
   Done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to