This is an automated email from the ASF dual-hosted git repository.

jfeinauer pushed a commit to branch feature/IOTDB-700-add-openid
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git

commit c2e146e76583caa298665d47fb9fe84836b3436f
Author: julian <[email protected]>
AuthorDate: Sun May 24 11:54:47 2020 +0200

    IOTBD-700 Initial test Implementation of OpenID Connect / JWT.
---
 server/pom.xml                                     |  20 ++
 .../iotdb/db/auth/authorizer/BasicAuthorizer.java  |  13 +-
 .../db/auth/authorizer/LocalFileAuthorizer.java    |   6 +
 .../iotdb/db/auth/authorizer/OpenIdAuthorizer.java | 234 +++++++++++++++++++++
 .../java/org/apache/iotdb/db/conf/IoTDBConfig.java |  11 +
 .../org/apache/iotdb/db/service/TSServiceImpl.java |   3 +-
 .../db/auth/authorizer/OpenIdAuthorizerTest.java   |  60 ++++++
 7 files changed, 341 insertions(+), 6 deletions(-)

diff --git a/server/pom.xml b/server/pom.xml
index b6cb127..924799d 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -145,6 +145,26 @@
             <artifactId>stream</artifactId>
             <version>2.9.5</version>
         </dependency>
+        <!--        compile group: 'io.jsonwebtoken', name: 'jjwt', version: 
'0.9.1'-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.10.7</version>
+        </dependency>
+        <!-- Impl -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>0.10.7</version>
+            <scope>runtime</scope>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson 
-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>0.10.7</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>
diff --git 
a/server/src/main/java/org/apache/iotdb/db/auth/authorizer/BasicAuthorizer.java 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/BasicAuthorizer.java
index 3659d2d..6bbeb1c 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/auth/authorizer/BasicAuthorizer.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/BasicAuthorizer.java
@@ -63,6 +63,9 @@ public abstract class BasicAuthorizer implements IAuthorizer, 
IService {
     logger.info("Initialization of Authorizer completes");
   }
 
+  /** Checks if a user has admin privileges */
+  abstract boolean isAdmin(String username);
+
   @Override
   public boolean login(String username, String password) throws AuthException {
     User user = userManager.getUser(username);
@@ -78,7 +81,7 @@ public abstract class BasicAuthorizer implements IAuthorizer, 
IService {
 
   @Override
   public void deleteUser(String username) throws AuthException {
-    if (IoTDBConstant.ADMIN_NAME.equals(username)) {
+    if (isAdmin(username)) {
       throw new AuthException("Default administrator cannot be deleted");
     }
     if (!userManager.deleteUser(username)) {
@@ -90,7 +93,7 @@ public abstract class BasicAuthorizer implements IAuthorizer, 
IService {
   public void grantPrivilegeToUser(String username, String path, int 
privilegeId)
       throws AuthException {
     String newPath = path;
-    if (IoTDBConstant.ADMIN_NAME.equals(username)) {
+    if (!isAdmin(username)) {
       throw new AuthException("Invalid operation, administrator already has 
all privileges");
     }
     if (!PrivilegeType.isPathRelevant(privilegeId)) {
@@ -105,7 +108,7 @@ public abstract class BasicAuthorizer implements 
IAuthorizer, IService {
   @Override
   public void revokePrivilegeFromUser(String username, String path, int 
privilegeId)
       throws AuthException {
-    if (IoTDBConstant.ADMIN_NAME.equals(username)) {
+    if (isAdmin(username)) {
       throw new AuthException("Invalid operation, administrator must have all 
privileges");
     }
     String p = path;
@@ -204,7 +207,7 @@ public abstract class BasicAuthorizer implements 
IAuthorizer, IService {
 
   @Override
   public Set<Integer> getPrivileges(String username, String path) throws 
AuthException {
-    if (IoTDBConstant.ADMIN_NAME.equals(username)) {
+    if (isAdmin(username)) {
       return ADMIN_PRIVILEGES;
     }
     User user = userManager.getUser(username);
@@ -233,7 +236,7 @@ public abstract class BasicAuthorizer implements 
IAuthorizer, IService {
   @Override
   public boolean checkUserPrivileges(String username, String path, int 
privilegeId)
       throws AuthException {
-    if (IoTDBConstant.ADMIN_NAME.equals(username)) {
+    if (isAdmin(username)) {
       return true;
     }
     User user = userManager.getUser(username);
diff --git 
a/server/src/main/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizer.java
 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizer.java
index c2d94ce..2a76648 100644
--- 
a/server/src/main/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizer.java
+++ 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/LocalFileAuthorizer.java
@@ -23,6 +23,7 @@ import org.apache.iotdb.db.auth.AuthException;
 import org.apache.iotdb.db.auth.role.LocalFileRoleManager;
 import org.apache.iotdb.db.auth.user.LocalFileUserManager;
 import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.conf.IoTDBConstant;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,6 +38,11 @@ public class LocalFileAuthorizer extends BasicAuthorizer {
         new LocalFileRoleManager(config.getSystemDir() + File.separator + 
"roles"));
   }
 
+  @Override
+  boolean isAdmin(String username) {
+    return IoTDBConstant.ADMIN_NAME.equals(username);
+  }
+
   /**
    * function for getting the instance of the local file authorizer.
    */
diff --git 
a/server/src/main/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizer.java
 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizer.java
new file mode 100644
index 0000000..5330000
--- /dev/null
+++ 
b/server/src/main/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizer.java
@@ -0,0 +1,234 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.auth.authorizer;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import org.apache.iotdb.db.auth.AuthException;
+import org.apache.iotdb.db.auth.role.IRoleManager;
+import org.apache.iotdb.db.auth.role.LocalFileRoleManager;
+import org.apache.iotdb.db.auth.user.IUserManager;
+import org.apache.iotdb.db.auth.user.LocalFileUserManager;
+import org.apache.iotdb.db.conf.IoTDBConfig;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Uses an OpenID Connect provider for Authorization / Authentication.
+ */
+public class OpenIdAuthorizer extends BasicAuthorizer {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(OpenIdAuthorizer.class);
+
+    private static IoTDBConfig config = 
IoTDBDescriptor.getInstance().getConfig();
+
+    private final String secret;
+
+    public OpenIdAuthorizer() throws AuthException {
+        this(config.getOpenIdSecret());
+    }
+
+    OpenIdAuthorizer(String secret) throws AuthException {
+        super(new LocalFileUserManager(config.getSystemDir() + File.separator 
+ "users"),
+                new LocalFileRoleManager(config.getSystemDir() + 
File.separator + "roles"));
+        if (secret == null) {
+            throw new IllegalArgumentException("OpenID Secret is null which is 
not allowed!");
+        }
+        this.secret = secret;
+    }
+
+    /**
+     * function for getting the instance of the local file authorizer.
+     */
+    public static OpenIdAuthorizer getInstance() throws AuthException {
+        if (OpenIdAuthorizer.InstanceHolder.instance == null) {
+            throw new AuthException("Authorizer uninitialized");
+        }
+        return OpenIdAuthorizer.InstanceHolder.instance;
+    }
+
+    private static class InstanceHolder {
+        private static OpenIdAuthorizer instance;
+
+        static {
+            // Only for testing here!
+            
IoTDBDescriptor.getInstance().getConfig().setOpenIdSecret("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
+            try {
+                instance = new OpenIdAuthorizer();
+            } catch (AuthException e) {
+                logger.error("Authorizer initialization failed due to ", e);
+                instance = null;
+            }
+        }
+    }
+
+    @Override
+    public boolean login(String token, String password) throws AuthException {
+        if (password != null && !password.isEmpty()) {
+            logger.error("JWT Login failed as a non-empty Password was given 
username (token): {}, password: {}", token, password);
+            return false;
+        }
+        if (token == null || token.isEmpty()) {
+            logger.error("JWT Login failed as a Username (token) was empty!");
+            return false;
+        }
+        //This line will throw an exception if it is not a signed JWS (as 
expected)
+        Claims claims;
+        try {
+            claims = validateToken(token);
+        } catch (JwtException e) {
+            logger.error("Unable to login the user wit jwt {}", password, e);
+            return false;
+        }
+        logger.debug("JWT was validated successfully!");
+        logger.debug("ID: {}", claims.getId());
+        logger.debug("Subject: {}", claims.getSubject());
+        logger.debug("Issuer: {}", claims.getIssuer());
+        logger.debug("Expiration: {}", claims.getExpiration());
+        // Create User if not exists
+        if (!super.listAllUsers().contains(claims.getId())) {
+            logger.info("User {} logs in for first time, storing it locally!", 
claims.getId());
+            super.createUser(claims.getSubject(), "UNUSED_PASSWORT");
+        }
+        return true;
+    }
+
+    private Claims validateToken(String token) throws JwtException {
+        return Jwts
+                .parser()
+                // Basically ignore the Expiration Date, if there is any???
+                .setAllowedClockSkewSeconds(Long.MAX_VALUE / 1000)
+                // .setSigningKey(DatatypeConverter.parseBase64Binary(secret))
+                .setSigningKey(secret.getBytes())
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    @Override
+    public void createUser(String username, String password) throws 
AuthException {
+        throw new UnsupportedOperationException("This operation is not 
supported for JWT Auth Provider!");
+    }
+
+    @Override
+    public void deleteUser(String username) throws AuthException {
+        throw new UnsupportedOperationException("This operation is not 
supported for JWT Auth Provider!");
+    }
+
+    @Override
+    boolean isAdmin(String token) {
+        Claims claims;
+        try {
+            claims = validateToken(token);
+        } catch (JwtException e) {
+            logger.warn("Unable to validate token {}!", token, e);
+            return false;
+        }
+        if (!(claims.get("IOTDB_ADMIN") instanceof Boolean) || 
!claims.get("IOTDB_ADMIN", Boolean.class)) {
+            logger.warn("Given Token has no admin rights, is custom claim 
IOTDB_ADMIN set to true?");
+            return false;
+        }
+        return true;
+    }
+
+//    @Override
+//    public void grantPrivilegeToUser(String username, String path, int 
privilegeId) throws AuthException {
+//        if (isAdmin(username)) {
+//            throw new AuthException("Given Token has no Admin privileges!");
+//        }
+//        // Yes, you are Admin! Gratz!
+//        // Do something here...
+//        super.grantPrivilegeToUser(username, path, privilegeId);
+//    }
+//    @Override
+//    public void revokePrivilegeFromUser(String username, String path, int 
privilegeId) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void createRole(String roleName) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void deleteRole(String roleName) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void grantPrivilegeToRole(String roleName, String path, int 
privilegeId) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void revokePrivilegeFromRole(String roleName, String path, int 
privilegeId) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void grantRoleToUser(String roleName, String username) throws 
AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void revokeRoleFromUser(String roleName, String username) throws 
AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public Set<Integer> getPrivileges(String username, String path) throws 
AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+
+    @Override
+    public void updateUserPassword(String username, String newPassword) throws 
AuthException {
+        throw new UnsupportedOperationException("This operation is not 
supported for JWT Auth Provider!");
+    }
+//
+//    @Override
+//    public boolean checkUserPrivileges(String username, String path, int 
privilegeId) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void reset() throws AuthException {
+//        // Do nothing
+//        super.reset();
+//    }
+//
+//    @Override
+//    public List<String> listAllUsers() {
+//        // Unsure if we list all "known" users or just throw this exception??
+//        throw new UnsupportedOperationException("This operation is not 
supported for JWT Auth Provider!");
+//    }
+//
+//    @Override
+//    public List<String> listAllRoles() {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public Role getRole(String roleName) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public User getUser(String username) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public boolean isUserUseWaterMark(String userName) throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+//
+//    @Override
+//    public void setUserUseWaterMark(String userName, boolean useWaterMark) 
throws AuthException {
+//        throw new NotImplementedException("Not yet implemented!");
+//    }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java 
b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
index 0d04592..ad3fabc 100644
--- a/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
+++ b/server/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
@@ -548,6 +548,9 @@ public class IoTDBConfig {
   // max size for tag and attribute of one time series
   private int tagAttributeTotalSize = 700;
 
+  // Open ID Secret
+  private String openIdSecret = null;
+
   public IoTDBConfig() {
     // empty constructor
   }
@@ -1506,4 +1509,12 @@ public class IoTDBConfig {
   public void setPrimitiveArraySize(int primitiveArraySize) {
     this.primitiveArraySize = primitiveArraySize;
   }
+
+  public String getOpenIdSecret() {
+    return openIdSecret;
+  }
+
+  public void setOpenIdSecret(String openIdSecret) {
+    this.openIdSecret = openIdSecret;
+  }
 }
diff --git 
a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java 
b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index 3beb9c9..052e27c 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -41,6 +41,7 @@ import org.apache.iotdb.db.auth.AuthException;
 import org.apache.iotdb.db.auth.AuthorityChecker;
 import org.apache.iotdb.db.auth.authorizer.IAuthorizer;
 import org.apache.iotdb.db.auth.authorizer.LocalFileAuthorizer;
+import org.apache.iotdb.db.auth.authorizer.OpenIdAuthorizer;
 import org.apache.iotdb.db.conf.IoTDBConfig;
 import org.apache.iotdb.db.conf.IoTDBConstant;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -180,7 +181,7 @@ public class TSServiceImpl implements TSIService.Iface, 
ServerContext {
     boolean status;
     IAuthorizer authorizer;
     try {
-      authorizer = LocalFileAuthorizer.getInstance();
+      authorizer = OpenIdAuthorizer.getInstance();
     } catch (AuthException e) {
       throw new TException(e);
     }
diff --git 
a/server/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java
 
b/server/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java
new file mode 100644
index 0000000..6470656
--- /dev/null
+++ 
b/server/src/test/java/org/apache/iotdb/db/auth/authorizer/OpenIdAuthorizerTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 ag [...]
+ */
+
+package org.apache.iotdb.db.auth.authorizer;
+
+import org.apache.iotdb.db.auth.AuthException;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class OpenIdAuthorizerTest {
+
+    @Test
+    public void loginWithJWT() throws AuthException {
+        String jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoiMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.PB603vtDyNkryxeLjomX1JQuSF2JHKXHyixzPBCA7tQ";
+        String secret = 
"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
+
+        OpenIdAuthorizer authorizer = new OpenIdAuthorizer(secret);
+        boolean login = authorizer.login(jwt, null);
+
+        assertTrue(login);
+    }
+
+    @Test
+    public void isAdmin_hasAccess() throws AuthException {
+        // IOTDB_ADMIN = true
+        String jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoiMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJJT1REQl9BRE1JTiI6dHJ1ZX0.dxB417n9GFAGbwL7kyIvgenEBycjlJLZbB1I_GF0qd8";
+        String secret = 
"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
+
+        OpenIdAuthorizer authorizer = new OpenIdAuthorizer(secret);
+        boolean admin = authorizer.isAdmin(jwt);
+
+        assertTrue(admin);
+    }
+
+    @Test
+    public void isAdmin_AdminClaimFalse() throws AuthException {
+        // IOTDB_ADMIN = false
+        String jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoiMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJJT1REQl9BRE1JTiI6ZmFsc2V9.80lCGEWhgW6YO55TFC98v_mj8ts0IcrBMb2drsxEpZ0";
+        String secret = 
"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
+
+        OpenIdAuthorizer authorizer = new OpenIdAuthorizer(secret);
+        boolean admin = authorizer.isAdmin(jwt);
+
+        assertFalse(admin);
+    }
+
+    @Test
+    public void isAdmin_noAdminClaim() throws AuthException {
+        // IOTDB_ADMIN = false
+        String jwt = 
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoiMTIzIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.PB603vtDyNkryxeLjomX1JQuSF2JHKXHyixzPBCA7tQ";
+        String secret = 
"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111";
+
+        OpenIdAuthorizer authorizer = new OpenIdAuthorizer(secret);
+        boolean admin = authorizer.isAdmin(jwt);
+
+        assertFalse(admin);
+    }
+}
\ No newline at end of file

Reply via email to