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
diff --git a/build.gradle b/build.gradle
index 8c6803e..960354d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -97,6 +97,7 @@
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.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
\ 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 @@
destroyed() {
// window.removeEventListener('hashchange', this.afterQRScan)
},
+ mounted() {
+ window.addEventListener('message', this.handleMessage)
+ },
methods: {
showPwd() {
if (this.passwordType === 'password') {
@@ -127,6 +133,12 @@
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 @@
.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 @@
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 @@
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 @@
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 @@
@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.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 @@
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 @@
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 @@
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 @@
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 @@
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.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 @@
# 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("test@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(), "test@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("test2@apache.org");
+ usersRepository.update(userInfoEntity);
+
+ userInfoEntityOptional = usersRepository.findByUserName(userInfoEntity.getName());
+ UserInfoEntity updateUserInfoEntity = userInfoEntityOptional.get();
+ Assert.assertEquals(updateUserInfoEntity.getPhoneNumber(), "1356789023456");
+ Assert.assertEquals(updateUserInfoEntity.getEmail(), "test2@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\": \"test@apache.org\",\n" +
+ "\t\"bio\": \"this is description\"" +
+ "}");
+ UserInfoEntity withTokenUserInfoEntity = thirdPartyLoginService.getUserInfo(authenticationMap);
+ Assert.assertEquals(withTokenUserInfoEntity.getEmail(), "test@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");
+ }
+}