mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: add gitlab provider (#273)
Signed-off-by: sh1luo <690898835@qq.com>
This commit is contained in:
parent
e1182bb635
commit
75e917a070
230
idp/gitlab.go
Normal file
230
idp/gitlab.go
Normal file
@ -0,0 +1,230 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GitlabIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
|
||||
idp := &GitlabIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://gitlab.com/oauth/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"read_user+profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type GitlabProviderToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
|
||||
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gtoken := &GitlabProviderToken{}
|
||||
if err = json.Unmarshal(data, gtoken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
|
||||
token := &oauth2.Token{
|
||||
AccessToken: gtoken.AccessToken,
|
||||
TokenType: gtoken.TokenType,
|
||||
RefreshToken: gtoken.RefreshToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(7200), 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id":5162115,
|
||||
"name":"shiluo",
|
||||
"username":"shiluo",
|
||||
"state":"active",
|
||||
"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
|
||||
"web_url":"https://gitlab.com/shiluo",
|
||||
"created_at":"2019-12-23T02:50:10.348Z",
|
||||
"bio":"",
|
||||
"bio_html":"",
|
||||
"location":"China",
|
||||
"public_email":"silo1999@163.com",
|
||||
"skype":"",
|
||||
"linkedin":"",
|
||||
"twitter":"",
|
||||
"website_url":"",
|
||||
"organization":"",
|
||||
"job_title":"",
|
||||
"pronouns":null,
|
||||
"bot":false,
|
||||
"work_information":null,
|
||||
"followers":0,
|
||||
"following":0,
|
||||
"last_sign_in_at":"2019-12-26T13:24:42.941Z",
|
||||
"confirmed_at":"2019-12-23T02:52:10.778Z",
|
||||
"last_activity_on":"2021-08-19",
|
||||
"email":"silo1999@163.com",
|
||||
"theme_id":1,
|
||||
"color_scheme_id":1,
|
||||
"projects_limit":100000,
|
||||
"current_sign_in_at":"2021-08-19T09:46:46.004Z",
|
||||
"identities":[
|
||||
{
|
||||
"provider":"github",
|
||||
"extern_uid":"51157931",
|
||||
"saml_provider_id":null
|
||||
}
|
||||
],
|
||||
"can_create_group":true,
|
||||
"can_create_project":true,
|
||||
"two_factor_enabled":false,
|
||||
"external":false,
|
||||
"private_profile":false,
|
||||
"commit_email":"silo1999@163.com",
|
||||
"shared_runners_minutes_limit":null,
|
||||
"extra_shared_runners_minutes_limit":null
|
||||
}
|
||||
*/
|
||||
|
||||
type GitlabUserInfo struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
State string `json:"state"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
WebUrl string `json:"web_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Bio string `json:"bio"`
|
||||
BioHtml string `json:"bio_html"`
|
||||
Location string `json:"location"`
|
||||
PublicEmail string `json:"public_email"`
|
||||
Skype string `json:"skype"`
|
||||
Linkedin string `json:"linkedin"`
|
||||
Twitter string `json:"twitter"`
|
||||
WebsiteUrl string `json:"website_url"`
|
||||
Organization string `json:"organization"`
|
||||
JobTitle string `json:"job_title"`
|
||||
Pronouns interface{} `json:"pronouns"`
|
||||
Bot bool `json:"bot"`
|
||||
WorkInformation interface{} `json:"work_information"`
|
||||
Followers int `json:"followers"`
|
||||
Following int `json:"following"`
|
||||
LastSignInAt time.Time `json:"last_sign_in_at"`
|
||||
ConfirmedAt time.Time `json:"confirmed_at"`
|
||||
LastActivityOn string `json:"last_activity_on"`
|
||||
Email string `json:"email"`
|
||||
ThemeId int `json:"theme_id"`
|
||||
ColorSchemeId int `json:"color_scheme_id"`
|
||||
ProjectsLimit int `json:"projects_limit"`
|
||||
CurrentSignInAt time.Time `json:"current_sign_in_at"`
|
||||
Identities []struct {
|
||||
Provider string `json:"provider"`
|
||||
ExternUid string `json:"extern_uid"`
|
||||
SamlProviderId interface{} `json:"saml_provider_id"`
|
||||
} `json:"identities"`
|
||||
CanCreateGroup bool `json:"can_create_group"`
|
||||
CanCreateProject bool `json:"can_create_project"`
|
||||
TwoFactorEnabled bool `json:"two_factor_enabled"`
|
||||
External bool `json:"external"`
|
||||
PrivateProfile bool `json:"private_profile"`
|
||||
CommitEmail string `json:"commit_email"`
|
||||
SharedRunnersMinutesLimit interface{} `json:"shared_runners_minutes_limit"`
|
||||
ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
|
||||
}
|
||||
|
||||
// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
|
||||
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token="+token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guser := GitlabUserInfo{}
|
||||
if err = json.Unmarshal(data, &guser);err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(guser.Id),
|
||||
Username: guser.Username,
|
||||
DisplayName: guser.Name,
|
||||
AvatarUrl: guser.AvatarUrl,
|
||||
Email: guser.Email,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
@ -57,6 +57,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
||||
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Lark" {
|
||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "GitLab" {
|
||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
func SendCodeToPhone(provider *Provider, phone, code string) string {
|
||||
client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
|
||||
if client == nil {
|
||||
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
|
||||
return fmt.Sprintf("Unsupported provider type: %s", provider.Type)
|
||||
}
|
||||
|
||||
param := make(map[string]string)
|
||||
|
@ -58,6 +58,7 @@ type User struct {
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
|
@ -77,6 +77,7 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'LinkedIn', name: 'LinkedIn'},
|
||||
{id: 'WeCom', name: 'WeCom'},
|
||||
{id: 'Lark', name: 'Lark'},
|
||||
{id: 'GitLab', name: 'GitLab'},
|
||||
]
|
||||
);
|
||||
} else if (provider.category === "Email") {
|
||||
|
32
web/src/auth/GitLabLoginButton.js
Normal file
32
web/src/auth/GitLabLoginButton.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 {createButton} from "react-social-login-buttons";
|
||||
import {StaticBaseUrl} from "../Setting";
|
||||
|
||||
function Icon({ width = 24, height = 24, color }) {
|
||||
return <img src={`${StaticBaseUrl}/buttons/gitlab.svg`} alt="Sign in with GitLab" style={{width: 24, height: 24}} />;
|
||||
}
|
||||
|
||||
const config = {
|
||||
text: "Sign in with GitLab",
|
||||
icon: Icon,
|
||||
iconFormat: name => `fa fa-${name}`,
|
||||
style: {background: "rgb(255,255,255)", color: "#000000"},
|
||||
activeStyle: {background: "rgb(100,150,250)"},
|
||||
};
|
||||
|
||||
const GitLabLoginButton = createButton(config);
|
||||
|
||||
export default GitLabLoginButton;
|
@ -32,6 +32,7 @@ import i18next from "i18next";
|
||||
import LinkedInLoginButton from "./LinkedInLoginButton";
|
||||
import WeComLoginButton from "./WeComLoginButton";
|
||||
import LarkLoginButton from "./LarkLoginButton";
|
||||
import GitLabLoginButton from "./GitLabLoginButton";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -170,6 +171,8 @@ class LoginPage extends React.Component {
|
||||
return <WeComLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Lark") {
|
||||
return <LarkLoginButton text={text} align={"center"} />
|
||||
} else if (type === "GitLab") {
|
||||
return <GitLabLoginButton text={text} align={"center"} />
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@ -55,10 +55,14 @@ const LinkedInAuthLogo = `${StaticBaseUrl}/img/social_linkedin.png`;
|
||||
const WeComAuthUri = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect";
|
||||
const WeComAuthLogo = `${StaticBaseUrl}/img/social_wecom.png`;
|
||||
|
||||
// const WeComAuthScope = "";
|
||||
// const LarkAuthScope = "";
|
||||
const LarkAuthUri = "https://open.feishu.cn/open-apis/authen/v1/index";
|
||||
const LarkAuthLogo = `${StaticBaseUrl}/img/social_lark.png`;
|
||||
|
||||
const GitLabAuthScope = "read_user+profile";
|
||||
const GitLabAuthUri = "https://gitlab.com/oauth/authorize";
|
||||
const GitLabAuthLogo = `${StaticBaseUrl}/img/social_gitlab.png`;
|
||||
|
||||
export function getAuthLogo(provider) {
|
||||
if (provider.type === "Google") {
|
||||
return GoogleAuthLogo;
|
||||
@ -82,6 +86,8 @@ export function getAuthLogo(provider) {
|
||||
return WeComAuthLogo;
|
||||
} else if (provider.type === "Lark") {
|
||||
return LarkAuthLogo;
|
||||
} else if (provider.type === "GitLab") {
|
||||
return GitLabAuthLogo;
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,5 +120,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${WeComAuthUri}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`
|
||||
} else if (provider.type === "Lark") {
|
||||
return `${LarkAuthUri}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`
|
||||
} else if (provider.type === "GitLab") {
|
||||
return `${GitLabAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${GitLabAuthScope}`
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user