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

sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-manager.git


The following commit(s) were added to refs/heads/master by this push:
     new 7138028  Support third party login for example GitHub (#222)
7138028 is described below

commit 713802827eb00214f295195de16f1c82351d0499
Author: tuteng <guangn...@apache.org>
AuthorDate: Thu Dec 5 03:04:26 2019 +0800

    Support third party login for example GitHub (#222)
    
    Fix #14
    
    ### Motivation
    This pr is used to support Github authentication. Next, I will add support 
for user authorization.
    
    ### Modifications
    
    * Support authentication by Github
    * Add user table
    * Add a third-party login page
    
    ### Verifying this change
    
     Add unit test
---
 build.gradle                                       |   1 +
 front-end/src/api/socialsignin.js                  |  23 ++++
 front-end/src/icons/svg/github.svg                 |   1 +
 front-end/src/views/login/index.vue                |  19 ++-
 front-end/src/views/login/socialsignin.vue         |  44 ++++---
 src/README.md                                      |  25 ++++
 .../ThirdPartyLoginCallbackController.java         | 113 ++++++++++++++++++
 .../pulsar/manager/dao/UsersRepositoryImpl.java    |  62 ++++++++++
 .../pulsar/manager/entity/GithubAuthEntity.java    |  36 ++++++
 .../manager/entity/GithubUserInfoEntity.java       | 108 +++++++++++++++++
 .../pulsar/manager/entity/UserInfoEntity.java      |  38 ++++++
 .../pulsar/manager/entity/UsersRepository.java     |  58 +++++++++
 .../manager/interceptor/WebAppConfigurer.java      |   4 +-
 .../apache/pulsar/manager/mapper/UsersMapper.java  |  54 +++++++++
 .../manager/service/ThirdPartyLoginService.java    |  37 ++++++
 .../service/impl/GithubLoginServiceImpl.java       | 112 ++++++++++++++++++
 .../org/apache/pulsar/manager/utils/HttpUtil.java  |  19 ++-
 src/main/resources/META-INF/sql/herddb-schema.sql  |  12 ++
 src/main/resources/META-INF/sql/mysql-schema.sql   |  13 +++
 .../resources/META-INF/sql/postgresql-schema.sql   |  13 +++
 src/main/resources/META-INF/sql/sqlite-schema.sql  |  12 ++
 src/main/resources/application.properties          |  24 +++-
 src/main/resources/templates/index.html            |  40 +++++++
 .../manager/dao/UsersRepositoryImplTest.java       | 100 ++++++++++++++++
 .../service/GithubLoginServiceImplTest.java        | 129 +++++++++++++++++++++
 25 files changed, 1069 insertions(+), 28 deletions(-)

diff --git a/build.gradle b/build.gradle
index 8c6803e..960354d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -97,6 +97,7 @@ dependencies {
     compile group: 'org.springframework.boot', name: 'spring-boot-devtools', 
version: springBootVersion
     compile group: 'org.springframework.cloud', name: 
'spring-cloud-starter-netflix-zuul', version: springBootVersion
     compile group: 'org.mybatis.spring.boot', name: 
'mybatis-spring-boot-starter', version: springMybatisVersion
+    compile group: 'org.springframework.boot', name: 
'spring-boot-starter-thymeleaf', version: springBootVersion
     compile group: 'org.postgresql', name: 'postgresql', version: 
postgresqlVersion
     compile group: 'org.herddb', name: 'herddb-jdbc', version: herddbVersion
     compile group: 'javax.validation', name: 'validation-api', version: 
javaxValidationVersion
diff --git a/front-end/src/api/socialsignin.js 
b/front-end/src/api/socialsignin.js
new file mode 100644
index 0000000..fee4336
--- /dev/null
+++ b/front-end/src/api/socialsignin.js
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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.
+ */
+import request from '@/utils/request'
+
+const BASE_URL = '/pulsar-manager/third-party-login'
+
+export function getGithubLoginHost() {
+  return request({
+    url: BASE_URL + `/github/login`,
+    method: 'get'
+  })
+}
diff --git a/front-end/src/icons/svg/github.svg 
b/front-end/src/icons/svg/github.svg
new file mode 100644
index 0000000..3899712
--- /dev/null
+++ b/front-end/src/icons/svg/github.svg
@@ -0,0 +1 @@
+<svg role="img" viewBox="0 0 24 24" 
xmlns="http://www.w3.org/2000/svg";><title>GitHub icon</title><path d="M12 
.297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 
11.385.6.113.82-.258.82-.577 
0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 
17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 
1.07 1.835 2.809 1.305 
3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 
0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3 [...]
\ No newline at end of file
diff --git a/front-end/src/views/login/index.vue 
b/front-end/src/views/login/index.vue
index 758714f..3d95039 100644
--- a/front-end/src/views/login/index.vue
+++ b/front-end/src/views/login/index.vue
@@ -53,6 +53,9 @@
       </el-form-item>
 
       <el-button :loading="loading" type="primary" 
style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">{{ 
$t('login.logIn') }}</el-button>
+      <el-button class="thirdparty-button" type="primary" 
@click="showDialog=true">
+        Or connect with
+      </el-button>
     </el-form>
 
     <el-dialog :title="$t('login.thirdparty')" :visible.sync="showDialog" 
append-to-body>
@@ -119,6 +122,9 @@ export default {
   destroyed() {
     // window.removeEventListener('hashchange', this.afterQRScan)
   },
+  mounted() {
+    window.addEventListener('message', this.handleMessage)
+  },
   methods: {
     showPwd() {
       if (this.passwordType === 'password') {
@@ -127,6 +133,12 @@ export default {
         this.passwordType = 'password'
       }
     },
+    handleMessage(event) {
+      const data = event.data
+      if (data.hasOwnProperty('name') && data.hasOwnProperty('accessToken')) {
+        // to do set token, track task 
https://github.com/apache/pulsar-manager/issues/14
+      }
+    },
     handleLogin() {
       this.$refs.loginForm.validate(valid => {
         if (valid) {
@@ -276,7 +288,12 @@ $light_gray:#eee;
   .thirdparty-button {
     position: absolute;
     right: 35px;
-    bottom: 28px;
+    bottom: 1px;
+  }
+  @media only screen and (max-width: 470px) {
+    .thirdparty-button {
+      display: none;
+    }
   }
 }
 </style>
diff --git a/front-end/src/views/login/socialsignin.vue 
b/front-end/src/views/login/socialsignin.vue
index d9bb8d7..51d1735 100644
--- a/front-end/src/views/login/socialsignin.vue
+++ b/front-end/src/views/login/socialsignin.vue
@@ -15,36 +15,33 @@
 -->
 <template>
   <div class="social-signup-container">
-    <div class="sign-btn" @click="wechatHandleClick('wechat')">
-      <span class="wx-svg-container"><svg-icon icon-class="wechat" 
class="icon"/></span> 微信
-    </div>
-    <div class="sign-btn" @click="tencentHandleClick('tencent')">
-      <span class="qq-svg-container"><svg-icon icon-class="qq" 
class="icon"/></span> QQ
+    <div class="sign-btn" @click="githubHandleClick('github')">
+      <span class="github-container"><svg-icon icon-class="github" 
class="icon"/></span> GitHub
     </div>
   </div>
 </template>
 
 <script>
-// import openWindow from '@/utils/openWindow'
+import openWindow from '@/utils/openWindow'
+import { getGithubLoginHost } from '@/api/socialsignin'
 
 export default {
   name: 'SocialSignin',
   methods: {
-    wechatHandleClick(thirdpart) {
-      alert('ok')
-      // this.$store.commit('SET_AUTH_TYPE', thirdpart)
-      // const appid = 'xxxxx'
-      // const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + 
window.location.origin + '/auth-redirect')
-      // const url = 'https://open.weixin.qq.com/connect/qrconnect?appid=' + 
appid + '&redirect_uri=' + redirect_uri + 
'&response_type=code&scope=snsapi_login#wechat_redirect'
-      // openWindow(url, thirdpart, 540, 540)
-    },
-    tencentHandleClick(thirdpart) {
-      alert('ok')
-      // this.$store.commit('SET_AUTH_TYPE', thirdpart)
-      // const client_id = 'xxxxx'
-      // const redirect_uri = encodeURIComponent('xxx/redirect?redirect=' + 
window.location.origin + '/auth-redirect')
-      // const url = 
'https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=' + 
client_id + '&redirect_uri=' + redirect_uri
-      // openWindow(url, thirdpart, 540, 540)
+    githubHandleClick(thirdpart) {
+      getGithubLoginHost().then(response => {
+        if (!response.data) return
+        if (response.data.message === 'success') {
+          openWindow(decodeURIComponent(response.data.url), thirdpart, 540, 
540)
+        } else {
+          this.$notify({
+            title: 'failed',
+            message: response.data.message,
+            type: 'error',
+            duration: 3000
+          })
+        }
+      })
     }
   }
 }
@@ -62,8 +59,7 @@ export default {
       font-size: 24px;
       margin-top: 8px;
     }
-    .wx-svg-container,
-    .qq-svg-container {
+    .github-container {
       display: inline-block;
       width: 40px;
       height: 40px;
@@ -74,7 +70,7 @@ export default {
       margin-bottom: 20px;
       margin-right: 5px;
     }
-    .wx-svg-container {
+    .github-container {
       background-color: #8ada53;
     }
     .qq-svg-container {
diff --git a/src/README.md b/src/README.md
index b18eaec..858f52d 100644
--- a/src/README.md
+++ b/src/README.md
@@ -101,3 +101,28 @@ export SECRET_KEY="file:///secret-key-path"
 docker run -it -p 9527:9527 -e REDIRECT_HOST=http://192.168.55.182 -e 
REDIRECT_PORT=9527 -e DRIVER_CLASS_NAME=org.postgresql.Driver -e 
URL='jdbc:postgresql://127.0.0.1:5432/pulsar_manager' -e USERNAME=pulsar -e 
PASSWORD=pulsar -e LOG_LEVEL=DEBUG -e JWT_TOKEN=$JWT_TOKEN -e 
PRIVATE_KEY=$PRIVATE_KEY -e PUBLIC_KEY=$PUBLIC_KEY -v $PWD:/data -v 
$PWD/secret-key-path:/pulsar-manager/secret-key-path 
apachepulsar/pulsar-manager:v0.1.0 /bin/sh
 ```
 
+### Enable Github Login
+
+#### Third party login options
+
+```
+# default empty, current options github
+third.party.login.option=
+```
+
+#### Github login configuration
+
+```
+# The client ID you received from GitHub when you registered 
https://github.com/settings/applications/new.
+github.client.id=your-client-id
+# The client secret you received from GitHub for your GitHub App.
+github.client.secret=your-client-secret
+github.oauth.host=https://github.com/login/oauth/access_token
+github.user.info=https://api.github.com/user
+github.login.host=https://github.com/login/oauth/authorize
+github.redirect.host=http://localhost:9527
+
+# Expiration time of token for third party platform, unit second.
+# 60 * 60 * 24 * 7
+user.access.token.expire=604800
+```
diff --git 
a/src/main/java/org/apache/pulsar/manager/controller/ThirdPartyLoginCallbackController.java
 
b/src/main/java/org/apache/pulsar/manager/controller/ThirdPartyLoginCallbackController.java
new file mode 100644
index 0000000..f9f175d
--- /dev/null
+++ 
b/src/main/java/org/apache/pulsar/manager/controller/ThirdPartyLoginCallbackController.java
@@ -0,0 +1,113 @@
+/**
+ * Licensed 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.pulsar.manager.controller;
+
+import com.google.common.collect.Maps;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+import org.apache.pulsar.manager.service.ThirdPartyLoginService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+/**
+ * Callback function of third party platform login.
+ */
+@Slf4j
+@Controller
+@RequestMapping(value = "/pulsar-manager/third-party-login")
+@Api(description = "Calling the request below this class does not require 
authentication because " +
+        "the user has not logged in yet.")
+@Validated
+public class ThirdPartyLoginCallbackController {
+
+    @Value("${github.client.id}")
+    private String githubClientId;
+
+    @Value("${github.login.host}")
+    private String githubLoginHost;
+
+    @Value("${github.redirect.host}")
+    private String githubRedirectHost;
+
+    @Autowired
+    private ThirdPartyLoginService thirdPartyLoginService;
+
+    @ApiOperation(value = "When use pass github authentication, Github 
platform will carry code parameter to call " +
+            "back this address actively. At this time, we can request token 
and get user information through " +
+            "this code." +
+            "Reference document: 
https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/";)
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "ok"),
+            @ApiResponse(code = 404, message = "Not found"),
+            @ApiResponse(code = 500, message = "Internal server error")
+    })
+    @RequestMapping(value = "/callback/github")
+    public String githubCallbackIndex(Model model, @RequestParam() String 
code) {
+        Map<String, String> parameters = Maps.newHashMap();
+        parameters.put("code", code);
+        String accessToken = thirdPartyLoginService.getAuthToken(parameters);
+        Map<String, String> authenticationMap = Maps.newHashMap();
+        authenticationMap.put("access_token", accessToken);
+        UserInfoEntity userInfoEntity = 
thirdPartyLoginService.getUserInfo(authenticationMap);
+        if (userInfoEntity == null) {
+            model.addAttribute("messages", "Authentication failed, please 
check carefully");
+            model.addAttribute("flag", false);
+            return "index";
+        }
+        model.addAttribute("message", "Authentication successful, logging in");
+        model.addAttribute("flag", true);
+        model.addAttribute("userInfo", userInfoEntity);
+        return "index";
+    }
+
+    @ApiOperation(value = "Github's third-party authorized login address, HTTP 
GET request, needs to carry " +
+            "client_id and redirect_host parameters. Parameter client_id and 
redirect_host needs to be applied " +
+            "from github platform 
https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/.";)
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "ok"),
+            @ApiResponse(code = 404, message = "Not found"),
+            @ApiResponse(code = 500, message = "Internal server error")
+    })
+    @RequestMapping(value = "/github/login", method = RequestMethod.GET)
+    public @ResponseBody ResponseEntity<Map<String, Object>> 
getGithubLoginUrl() {
+        Map<String, Object> result = Maps.newHashMap();
+        String url = githubLoginHost + "?client_id=" + githubClientId +
+                "&redirect_host=" + githubRedirectHost + 
"/pulsar-manager/third-party-login/callback/github";
+        try {
+            result.put("url", URLEncoder.encode(url, 
StandardCharsets.UTF_8.toString()));
+            result.put("message", "success");
+        } catch (UnsupportedEncodingException e) {
+            log.error("Url encoding failed, please check: [{}]", url);
+            result.put("message", "Url encoding failed, please check:: " + 
url);
+        }
+        return ResponseEntity.ok(result);
+    }
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/dao/UsersRepositoryImpl.java 
b/src/main/java/org/apache/pulsar/manager/dao/UsersRepositoryImpl.java
new file mode 100644
index 0000000..410c5f1
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/dao/UsersRepositoryImpl.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed 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.pulsar.manager.dao;
+
+import com.github.pagehelper.Page;
+import com.github.pagehelper.PageHelper;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+import org.apache.pulsar.manager.entity.UsersRepository;
+import org.apache.pulsar.manager.mapper.UsersMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public class UsersRepositoryImpl implements UsersRepository {
+
+    private final UsersMapper usersMapper;
+
+    @Autowired
+    public UsersRepositoryImpl(UsersMapper usersMapper) {
+        this.usersMapper = usersMapper;
+    }
+
+    @Override
+    public long save(UserInfoEntity userInfoEntity) {
+        long userId = this.usersMapper.save(userInfoEntity);
+        return userId;
+    }
+
+    @Override
+    public Optional<UserInfoEntity> findByUserName(String name) {
+        return Optional.ofNullable(this.usersMapper.findByUserName(name));
+    }
+
+    @Override
+    public Page<UserInfoEntity> findUsersList(Integer pageNum, Integer 
pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        return this.usersMapper.findUsersList();
+    }
+
+    @Override
+    public void update(UserInfoEntity userInfoEntity) {
+        this.usersMapper.update(userInfoEntity);
+    }
+
+    @Override
+    public void delete(String name) {
+        this.usersMapper.delete(name);
+    }
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/entity/GithubAuthEntity.java 
b/src/main/java/org/apache/pulsar/manager/entity/GithubAuthEntity.java
new file mode 100644
index 0000000..986ce05
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/entity/GithubAuthEntity.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed 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.pulsar.manager.entity;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Github auth information entity.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+public class GithubAuthEntity {
+
+    @SerializedName("access_token")
+    private String accessToken;
+
+    @SerializedName("token_type")
+    private String token_type;
+
+    private String scope;
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/entity/GithubUserInfoEntity.java 
b/src/main/java/org/apache/pulsar/manager/entity/GithubUserInfoEntity.java
new file mode 100644
index 0000000..0409454
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/entity/GithubUserInfoEntity.java
@@ -0,0 +1,108 @@
+/**
+ * Licensed 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.pulsar.manager.entity;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Github user information entity.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@Data
+public class GithubUserInfoEntity {
+    private String login;
+    private Long id;
+
+    @SerializedName("node_id")
+    private String nodeId;
+
+    @SerializedName("avatar_url")
+    private String avatarUrl;
+
+    @SerializedName("gravatar_id")
+    private String gravatarId;
+
+    private String url;
+
+    @SerializedName("html_url")
+    private String htmlUrl;
+
+    @SerializedName("followers_url")
+    private String followersUrl;
+
+    @SerializedName("following_url")
+    private String followingUrl;
+
+    @SerializedName("gists_url")
+    private String gistsUrl;
+
+    @SerializedName("starred_url")
+    private String starredUrl;
+
+    @SerializedName("subscriptions_url")
+    private String subscriptionsUrl;
+
+    @SerializedName("organizations_url")
+    private String organizationsUrl;
+
+    @SerializedName("repos_url")
+    private String reposUrl;
+
+    @SerializedName("events_url")
+    private String eventsUrl;
+
+    @SerializedName("received_events_url")
+    private String receivedEventsUrl;
+
+    private String type;
+
+    @SerializedName("site_admin")
+    private String siteAdmin;
+
+    private String name;
+
+    private String company;
+
+    private String blog;
+
+    private String location;
+
+    private String email;
+
+    private String hireable;
+
+    private String bio;
+
+    @SerializedName("public_repos")
+    private Integer publicRepos;
+
+    @SerializedName("public_gists")
+    private Integer publicGists;
+
+    private Long followers;
+
+    private Long following;
+
+    @SerializedName("created_at")
+    private String createdAt;
+
+    @SerializedName("updated_at")
+    private String updatedAt;
+}
diff --git a/src/main/java/org/apache/pulsar/manager/entity/UserInfoEntity.java 
b/src/main/java/org/apache/pulsar/manager/entity/UserInfoEntity.java
new file mode 100644
index 0000000..bff8d54
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/entity/UserInfoEntity.java
@@ -0,0 +1,38 @@
+/**
+ * Licensed 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.pulsar.manager.entity;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * Pulsar Manager platform entity.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@Data
+public class UserInfoEntity {
+    private long userId;
+    private String name;
+    private String description;
+    private String location;
+    private String company;
+    private String phoneNumber;
+    private String email;
+    private String accessToken;
+    private long expire;
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/entity/UsersRepository.java 
b/src/main/java/org/apache/pulsar/manager/entity/UsersRepository.java
new file mode 100644
index 0000000..fa467bb
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/entity/UsersRepository.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed 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.pulsar.manager.entity;
+
+import com.github.pagehelper.Page;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UsersRepository  {
+
+    /**
+     * Create a user.
+     * @param userInfoEntity
+     * @return user id
+     */
+    long save(UserInfoEntity userInfoEntity);
+
+    /**
+     * Get a user information by user name.
+     * @param name The user name
+     * @return UserInfoEntity
+     */
+    Optional<UserInfoEntity> findByUserName(String name);
+
+
+    /**
+     * Get user list, support paging.
+     * @param pageNum Get the data on which page.
+     * @param pageSize The number of data per page
+     * @return A list of UserInfoEntity.
+     */
+    Page<UserInfoEntity> findUsersList(Integer pageNum, Integer pageSize);
+
+    /**
+     * Update a user information.
+     * @param userInfoEntity UserInfoEntity
+     */
+    void update(UserInfoEntity userInfoEntity);
+
+    /**
+     * Delete a user by username.
+     * @param name username
+     */
+    void delete(String name);
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/interceptor/WebAppConfigurer.java 
b/src/main/java/org/apache/pulsar/manager/interceptor/WebAppConfigurer.java
index 818e294..c79f4b8 100644
--- a/src/main/java/org/apache/pulsar/manager/interceptor/WebAppConfigurer.java
+++ b/src/main/java/org/apache/pulsar/manager/interceptor/WebAppConfigurer.java
@@ -27,6 +27,8 @@ public class WebAppConfigurer implements WebMvcConfigurer {
 
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
-        
registry.addInterceptor(adminHandlerInterceptor).addPathPatterns("/**").excludePathPatterns("/pulsar-manager/login");
+        registry.addInterceptor(adminHandlerInterceptor).addPathPatterns("/**")
+                .excludePathPatterns("/pulsar-manager/login")
+                .excludePathPatterns("/pulsar-manager/third-party-login/**");
     }
 }
diff --git a/src/main/java/org/apache/pulsar/manager/mapper/UsersMapper.java 
b/src/main/java/org/apache/pulsar/manager/mapper/UsersMapper.java
new file mode 100644
index 0000000..d792c12
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/mapper/UsersMapper.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed 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.pulsar.manager.mapper;
+
+import com.github.pagehelper.Page;
+import org.apache.ibatis.annotations.Delete;
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Options;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+
+@Mapper
+public interface UsersMapper {
+
+    @Insert("INSERT INTO users (access_token, name, description, email, 
phone_number" +
+            ", location, company, expire)" +
+            "VALUES (#{accessToken}, #{name}, #{description}, #{email}, 
#{phoneNumber}" +
+            ", #{location}, #{company}, #{expire})")
+    @Options(useGeneratedKeys=true, keyProperty="userId", keyColumn="user_id")
+    long save(UserInfoEntity userInfoEntity);
+
+    @Select("SELECT access_token AS accessToken, user_id AS userId, name, 
description, email," +
+            "phone_number AS phoneNumber, location, company, expire " +
+            "FROM users " +
+            "WHERE name = #{name}")
+    UserInfoEntity findByUserName(String name);
+
+    @Select("SELECT access_token AS accessToken, user_id AS userId, name, 
description, email," +
+            "phone_number AS phoneNumber, location, company, expire " +
+            "FROM users")
+    Page<UserInfoEntity> findUsersList();
+
+    @Update("UPDATE users " +
+            "SET access_token = #{accessToken}, description = #{description}, 
email = #{email}," +
+            "phone_number = #{phoneNumber}, location = #{location}, company = 
#{company}, expire=#{expire} " +
+            "WHERE name = #{name}")
+    void update(UserInfoEntity userInfoEntity);
+
+    @Delete("DELETE FROM users WHERE name=#{name}")
+    void delete(String name);
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/service/ThirdPartyLoginService.java 
b/src/main/java/org/apache/pulsar/manager/service/ThirdPartyLoginService.java
new file mode 100644
index 0000000..9f44bd7
--- /dev/null
+++ 
b/src/main/java/org/apache/pulsar/manager/service/ThirdPartyLoginService.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed 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.pulsar.manager.service;
+
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+
+import java.util.Map;
+
+public interface ThirdPartyLoginService {
+
+    /**
+     * Obtaining an authentication token from a third-party platform.
+     * @param parameters For a kv type map, different third-party platforms 
may need to pass different parameters,
+     *                  which are passed according to the actual situation and 
analyzed in their implementation classes.
+     * @return String format access token information
+     */
+    String getAuthToken(Map<String, String> parameters);
+
+    /**
+     * Acquiring user information according to an authentication token.
+     * @param authenticationMap For a kv type map, different third-party 
platforms need different parameters,
+     *                          which are passed through the map structure.
+     * @return UserInfoEntity
+     */
+    UserInfoEntity getUserInfo(Map<String, String> authenticationMap);
+}
diff --git 
a/src/main/java/org/apache/pulsar/manager/service/impl/GithubLoginServiceImpl.java
 
b/src/main/java/org/apache/pulsar/manager/service/impl/GithubLoginServiceImpl.java
new file mode 100644
index 0000000..4a2eacb
--- /dev/null
+++ 
b/src/main/java/org/apache/pulsar/manager/service/impl/GithubLoginServiceImpl.java
@@ -0,0 +1,112 @@
+/**
+ * Licensed 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.pulsar.manager.service.impl;
+
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.pulsar.manager.entity.GithubAuthEntity;
+import org.apache.pulsar.manager.entity.GithubUserInfoEntity;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+import org.apache.pulsar.manager.service.ThirdPartyLoginService;
+import org.apache.pulsar.manager.utils.HttpUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+/**
+ * Github login for get user information.
+ */
+@Slf4j
+@Service
+public class GithubLoginServiceImpl implements ThirdPartyLoginService {
+
+    @Value("${github.client.id}")
+    private String githubClientId;
+
+    @Value("${github.client.secret}")
+    private String githubClientSecret;
+
+    @Value("${github.oauth.host}")
+    private String githubAuthHost;
+
+    @Value("${github.user.info}")
+    private String githubUserInfo;
+
+    /**
+     * Get user access token from github.
+     * @param parameters For get code to github.
+     *          GitHub redirects back to local site with a temporary code in a 
code parameter as well as the state
+     *          you provided in the previous step in a state parameter.The 
temporary code will expire after 10 minutes.
+     * @return access_token of string type
+     */
+    public String getAuthToken(Map<String, String> parameters) {
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        header.put("Accept", "application/json");
+        Map<String, Object> body = Maps.newHashMap();
+        body.put("client_id", githubClientId);
+        body.put("client_secret", githubClientSecret);
+        if (!parameters.containsKey("code")) {
+            log.error("Parameter does not contain code field, which is 
illegal.");
+            return null;
+        }
+        body.put("code", parameters.get("code"));
+        Gson gson = new Gson();
+        try {
+            // result example: access_token=your-token&token_type=bearer
+            String result = HttpUtil.doPost(githubAuthHost, header, 
gson.toJson(body));
+            GithubAuthEntity githubAuthEntity = gson.fromJson(result, 
GithubAuthEntity.class);
+            log.info("Success get access token from github");
+            return githubAuthEntity.getAccessToken();
+        } catch (UnsupportedEncodingException e) {
+            log.error("Failed get access token from github, error stack: {}", 
e.getCause());
+            return null;
+        }
+    }
+
+    /**
+     * Get user information from github by access token.
+     * @param authenticationMap Authentication mark requesting user 
information.
+     * @return UserInfoEntity
+     */
+    public UserInfoEntity getUserInfo(Map<String, String> authenticationMap) {
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        if (!authenticationMap.containsKey("access_token")) {
+            log.error("The authenticationMap does not contain access_token 
field, which is illegal.");
+            return null;
+        }
+        header.put("Authorization", "token " + 
authenticationMap.get("access_token"));
+        String result = HttpUtil.doGet(githubUserInfo, header);
+        Gson gson = new Gson();
+        GithubUserInfoEntity githubUserInfoEntity = gson.fromJson(result, 
GithubUserInfoEntity.class);
+        if (githubUserInfoEntity == null) {
+            log.error("Get user information from github failed.");
+            return null;
+        }
+        UserInfoEntity userInfoEntity = new UserInfoEntity();
+        userInfoEntity.setCompany(githubUserInfoEntity.getCompany());
+        // User 'login' field of github as platform name, because name field 
of github often empty.
+        // Github's name field is more like an alias.
+        userInfoEntity.setName(githubUserInfoEntity.getLogin());
+        userInfoEntity.setDescription(githubUserInfoEntity.getBio());
+        userInfoEntity.setEmail(githubUserInfoEntity.getEmail());
+        userInfoEntity.setLocation(githubUserInfoEntity.getLocation());
+        userInfoEntity.setAccessToken(authenticationMap.get("access_token"));
+        return userInfoEntity;
+    }
+}
diff --git a/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java 
b/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
index f08b7d4..cf0ba45 100644
--- a/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
+++ b/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
@@ -17,6 +17,7 @@ import org.apache.http.HttpStatus;
 import org.apache.http.client.config.RequestConfig;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.entity.StringEntity;
@@ -59,7 +60,23 @@ public class HttpUtil {
         return httpRequest(request, header);
     }
 
-    public static String doPut(String url, Map<String, String> header, String 
body) throws UnsupportedEncodingException {
+    /**
+     * HTTP post method.
+     * @param url Destination host
+     * @param header Header information
+     * @param body Body information
+     * @return HTTP response information
+     * @throws UnsupportedEncodingException
+     */
+    public static String doPost(String url, Map<String, String> header, String 
body)
+            throws UnsupportedEncodingException {
+        HttpPost request = new HttpPost(url);
+        request.setEntity(new StringEntity(body));
+        return httpRequest(request, header);
+    }
+
+    public static String doPut(String url, Map<String, String> header, String 
body)
+            throws UnsupportedEncodingException {
         HttpPut request = new HttpPut(url);
         request.setEntity(new StringEntity(body));
         return httpRequest(request, header);
diff --git a/src/main/resources/META-INF/sql/herddb-schema.sql 
b/src/main/resources/META-INF/sql/herddb-schema.sql
index 61d7469..28fc09a 100644
--- a/src/main/resources/META-INF/sql/herddb-schema.sql
+++ b/src/main/resources/META-INF/sql/herddb-schema.sql
@@ -113,3 +113,15 @@ CREATE TABLE IF NOT EXISTS tokens (
   description varchar(128),
   token varchar(1024)
 );
+
+CREATE TABLE IF NOT EXISTS users (
+  user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
+  access_token varchar(256) NOT NULL,
+  name varchar(256) NOT NULL,
+  description varchar(128),
+  email varchar(256),
+  phone_number varchar(48),
+  location varchar(256),
+  company varchar(256),
+  expire LONG NOT NULL
+);
diff --git a/src/main/resources/META-INF/sql/mysql-schema.sql 
b/src/main/resources/META-INF/sql/mysql-schema.sql
index ba22fbb..4607a31 100644
--- a/src/main/resources/META-INF/sql/mysql-schema.sql
+++ b/src/main/resources/META-INF/sql/mysql-schema.sql
@@ -122,4 +122,17 @@ CREATE TABLE IF NOT EXISTS tokens (
   description varchar(128),
   token varchar(1024),
   UNIQUE (role)
+)ENGINE=InnoDB CHARACTER SET utf8;
+
+CREATE TABLE IF NOT EXISTS users (
+  user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
+  access_token varchar(256) NOT NULL,
+  name varchar(256) NOT NULL,
+  description varchar(128),
+  email varchar(256),
+  phone_number varchar(48),
+  location varchar(256),
+  company varchar(256),
+  expire LONG NOT NULL,
+  UNIQUE (name)
 )ENGINE=InnoDB CHARACTER SET utf8;
\ No newline at end of file
diff --git a/src/main/resources/META-INF/sql/postgresql-schema.sql 
b/src/main/resources/META-INF/sql/postgresql-schema.sql
index 4f4ccf4..bf55254 100644
--- a/src/main/resources/META-INF/sql/postgresql-schema.sql
+++ b/src/main/resources/META-INF/sql/postgresql-schema.sql
@@ -122,4 +122,17 @@ CREATE TABLE IF NOT EXISTS tokens (
   description varchar(128),
   token varchar(1024) NOT NUll,
   UNIQUE (role)
+);
+
+CREATE TABLE IF NOT EXISTS users (
+  user_id BIGSERIAL PRIMARY KEY AUTO_INCREMENT,
+  access_token varchar(256) NOT NULL,
+  name varchar(256) NOT NULL,
+  description varchar(128),
+  email varchar(256),
+  phone_number varchar(48),
+  location varchar(256),
+  company varchar(256),
+  expire BIGINT NOT NULL,
+  UNIQUE (name)
 );
\ No newline at end of file
diff --git a/src/main/resources/META-INF/sql/sqlite-schema.sql 
b/src/main/resources/META-INF/sql/sqlite-schema.sql
index 7e6aa09..4ae82be 100644
--- a/src/main/resources/META-INF/sql/sqlite-schema.sql
+++ b/src/main/resources/META-INF/sql/sqlite-schema.sql
@@ -120,4 +120,16 @@ CREATE TABLE IF NOT EXISTS tokens (
   UNIQUE (role)
 );
 
+CREATE TABLE IF NOT EXISTS users (
+  user_id integer PRIMARY KEY AUTOINCREMENT,
+  access_token varchar(256) NOT NULL,
+  name varchar(256) NOT NULL,
+  description varchar(128),
+  email varchar(256),
+  phone_number varchar(48),
+  location varchar(256),
+  company varchar(256),
+  expire integer NOT NUll,
+  UNIQUE (name)
+);
 
diff --git a/src/main/resources/application.properties 
b/src/main/resources/application.properties
index dbace83..9552e75 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -20,7 +20,7 @@ logging.path=
 logging.file=pulsar-manager.log
 
 # DEBUG print execute sql
-logging.level.org.apache=DEBUG
+logging.level.org.apache=INFO
 
 mybatis.type-aliases-package=org.apache.pulsar.manager
 
@@ -105,3 +105,25 @@ init.delay.interval=0
 
 # cluster data reload
 cluster.cache.reload.interval.ms=60000
+
+# Third party login options
+third.party.login.option=
+
+# Github login configuration
+github.client.id=your-client-id
+github.client.secret=your-client-secret
+github.oauth.host=https://github.com/login/oauth/access_token
+github.user.info=https://api.github.com/user
+github.login.host=https://github.com/login/oauth/authorize
+github.redirect.host=http://localhost:9527
+
+user.access.token.expire=604800
+
+# thymeleaf configuration for third login.
+spring.thymeleaf.cache=false
+spring.thymeleaf.prefix=classpath:/templates/
+spring.thymeleaf.check-template-location=true
+spring.thymeleaf.suffix=.html
+spring.thymeleaf.encoding=UTF-8
+spring.thymeleaf.servlet.content-type=text/html
+spring.thymeleaf.mode=HTML5
diff --git a/src/main/resources/templates/index.html 
b/src/main/resources/templates/index.html
new file mode 100644
index 0000000..9cd4011
--- /dev/null
+++ b/src/main/resources/templates/index.html
@@ -0,0 +1,40 @@
+<!--
+
+    Licensed 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.
+
+-->
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org";>
+<head>
+    <meta charset="utf-8">
+    <title>Pulsar Manager</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <meta name="viewport" content="width=device-width, initial-scale=1, 
maximum-scale=1">
+</head>
+<body>
+<div>
+    <p th:utext="${message}"></p>
+</div>
+</body>
+<script th:inline="javascript">
+    window.onload = function() {
+        var flag = [[${flag}]]
+        if (flag) {
+            var userInfo = [[${userInfo}]]
+            var targetOrigin = window.opener;
+            targetOrigin.postMessage(userInfo, 'http://' + 
window.location.host);
+            window.close();
+        }
+    }
+</script>
+</html>
\ No newline at end of file
diff --git 
a/src/test/java/org/apache/pulsar/manager/dao/UsersRepositoryImplTest.java 
b/src/test/java/org/apache/pulsar/manager/dao/UsersRepositoryImplTest.java
new file mode 100644
index 0000000..ac08a7a
--- /dev/null
+++ b/src/test/java/org/apache/pulsar/manager/dao/UsersRepositoryImplTest.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed 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.pulsar.manager.dao;
+
+import com.github.pagehelper.Page;
+import org.apache.pulsar.manager.PulsarManagerApplication;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+import org.apache.pulsar.manager.entity.UsersRepository;
+import org.apache.pulsar.manager.profiles.HerdDBTestProfile;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.Optional;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        classes = {
+                PulsarManagerApplication.class,
+                HerdDBTestProfile.class
+        }
+)
+@ActiveProfiles("test")
+public class UsersRepositoryImplTest {
+
+    @Autowired
+    private UsersRepository usersRepository;
+
+    private void initUser(UserInfoEntity userInfoEntity) {
+        userInfoEntity.setAccessToken("test-access-token");
+        userInfoEntity.setEmail("t...@apache.org");
+        userInfoEntity.setLocation("bj");
+        userInfoEntity.setDescription("test-description");
+        userInfoEntity.setName("test-user");
+        userInfoEntity.setExpire(157900045678l);
+        userInfoEntity.setPhoneNumber("1356789023456");
+    }
+
+    private void validateUser(UserInfoEntity user) {
+        Assert.assertEquals(user.getName(), "test-user");
+        Assert.assertEquals(user.getExpire(), 157900045678l);
+        Assert.assertEquals(user.getPhoneNumber(), "1356789023456");
+        Assert.assertEquals(user.getDescription(), "test-description");
+        Assert.assertEquals(user.getLocation(), "bj");
+        Assert.assertEquals(user.getEmail(), "t...@apache.org");
+        Assert.assertEquals(user.getAccessToken(), "test-access-token");
+    }
+
+    @Test
+    public void getUsersListTest() {
+        UserInfoEntity userInfoEntity = new UserInfoEntity();
+        initUser(userInfoEntity);
+
+        usersRepository.save(userInfoEntity);
+
+        Page<UserInfoEntity> userInfoEntities = 
usersRepository.findUsersList(1, 10);
+        userInfoEntities.count(true);
+        userInfoEntities.getResult().forEach((user) -> {
+            validateUser(user);
+            usersRepository.delete(user.getName());
+        });
+    }
+
+    @Test
+    public void getAndUpdateUserTest() {
+        UserInfoEntity userInfoEntity = new UserInfoEntity();
+        initUser(userInfoEntity);
+        usersRepository.save(userInfoEntity);
+        Optional<UserInfoEntity> userInfoEntityOptional = 
usersRepository.findByUserName(userInfoEntity.getName());
+        UserInfoEntity getUserInfoEntity = userInfoEntityOptional.get();
+        validateUser(getUserInfoEntity);
+        userInfoEntity.setPhoneNumber("1356789023456");
+        userInfoEntity.setEmail("te...@apache.org");
+        usersRepository.update(userInfoEntity);
+
+        userInfoEntityOptional = 
usersRepository.findByUserName(userInfoEntity.getName());
+        UserInfoEntity updateUserInfoEntity = userInfoEntityOptional.get();
+        Assert.assertEquals(updateUserInfoEntity.getPhoneNumber(), 
"1356789023456");
+        Assert.assertEquals(updateUserInfoEntity.getEmail(), 
"te...@apache.org");
+
+        usersRepository.delete(updateUserInfoEntity.getName());
+        userInfoEntityOptional = 
usersRepository.findByUserName(userInfoEntity.getName());
+        Assert.assertFalse(userInfoEntityOptional.isPresent());
+    }
+}
diff --git 
a/src/test/java/org/apache/pulsar/manager/service/GithubLoginServiceImplTest.java
 
b/src/test/java/org/apache/pulsar/manager/service/GithubLoginServiceImplTest.java
new file mode 100644
index 0000000..4dadd54
--- /dev/null
+++ 
b/src/test/java/org/apache/pulsar/manager/service/GithubLoginServiceImplTest.java
@@ -0,0 +1,129 @@
+/**
+ * Licensed 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.pulsar.manager.service;
+
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
+import org.apache.pulsar.manager.PulsarManagerApplication;
+import org.apache.pulsar.manager.entity.UserInfoEntity;
+import org.apache.pulsar.manager.profiles.HerdDBTestProfile;
+import org.apache.pulsar.manager.utils.HttpUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+@RunWith(PowerMockRunner.class)
+@PowerMockRunnerDelegate(SpringRunner.class)
+@PowerMockIgnore( {"javax.*", "sun.*", "com.sun.*", "org.xml.*", "org.w3c.*"})
+@PrepareForTest(HttpUtil.class)
+@SpringBootTest(
+        classes = {
+                PulsarManagerApplication.class,
+                HerdDBTestProfile.class
+        }
+)
+@ActiveProfiles("test")
+public class GithubLoginServiceImplTest {
+
+    @Autowired
+    private ThirdPartyLoginService thirdPartyLoginService;
+
+    @Value("${github.client.id}")
+    private String githubClientId;
+
+    @Value("${github.client.secret}")
+    private String githubClientSecret;
+
+    @Value("${github.oauth.host}")
+    private String githubAuthHost;
+
+    @Value("${github.user.info}")
+    private String githubUserInfo;
+
+    @Test
+    public void getAuthTokenTest() throws UnsupportedEncodingException {
+        PowerMockito.mockStatic(HttpUtil.class);
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        header.put("Accept", "application/json");
+        Map<String, String> parameters = Maps.newHashMap();
+
+        // Test no code
+        String noCodeResult = thirdPartyLoginService.getAuthToken(parameters);
+        Assert.assertNull(noCodeResult);
+
+        // Test with code
+        parameters.put("code", "test-code");
+        Gson gson = new Gson();
+        Map<String, String> body = Maps.newHashMap();
+        body.put("code", parameters.get("code"));
+        body.put("client_id", githubClientId);
+        body.put("client_secret", githubClientSecret);
+        PowerMockito.when(HttpUtil.doPost(githubAuthHost, header, 
gson.toJson(body)))
+                .thenReturn("{" +
+                    "\"access_token\": 
\"e72e16c7e42f292c6912e7710c838347ae178b4a\"," +
+                    "\"scope\": \"repo,gist\"," +
+                    "\"token_type\": \"bearer\"" +
+                "}");
+        String withCodeResult = 
thirdPartyLoginService.getAuthToken(parameters);
+        Assert.assertEquals(withCodeResult, 
"e72e16c7e42f292c6912e7710c838347ae178b4a");
+    }
+
+    @Test
+    public void getUserInfoTest() {
+
+        Map<String, String> authenticationMap = Maps.newHashMap();
+        UserInfoEntity noTokenUserInfoEntity = 
thirdPartyLoginService.getUserInfo(authenticationMap);
+
+        Assert.assertEquals(noTokenUserInfoEntity, null);
+
+        authenticationMap.put("access_token", "test-user-token");
+        PowerMockito.mockStatic(HttpUtil.class);
+        Map<String, String> header = Maps.newHashMap();
+        header.put("Content-Type", "application/json");
+        header.put("Authorization", "token test-user-token");
+        PowerMockito.when(HttpUtil.doGet(githubUserInfo, header))
+                .thenReturn(null);
+        UserInfoEntity withTokenNullUserInfoEntity = 
thirdPartyLoginService.getUserInfo(authenticationMap);
+        Assert.assertNull(withTokenNullUserInfoEntity);
+        PowerMockito.when(HttpUtil.doGet(githubUserInfo, header))
+                .thenReturn("{\n" +
+                        "\t\"login\": \"test1\",\n" +
+                        "\t\"company\": bj,\n" +
+                        "\t\"location\": \"nw\",\n" +
+                        "\t\"email\": \"t...@apache.org\",\n" +
+                        "\t\"bio\": \"this is description\"" +
+                        "}");
+        UserInfoEntity withTokenUserInfoEntity = 
thirdPartyLoginService.getUserInfo(authenticationMap);
+        Assert.assertEquals(withTokenUserInfoEntity.getEmail(), 
"t...@apache.org");
+        Assert.assertEquals(withTokenUserInfoEntity.getName(), "test1");
+        Assert.assertEquals(withTokenUserInfoEntity.getCompany(), "bj");
+        Assert.assertEquals(withTokenUserInfoEntity.getDescription(), "this is 
description");
+        Assert.assertEquals(withTokenUserInfoEntity.getLocation(), "nw");
+        Assert.assertEquals(withTokenUserInfoEntity.getAccessToken(), 
"test-user-token");
+    }
+}

Reply via email to