Skip to content

Commit

Permalink
feat: add gitlab provider (#273)
Browse files Browse the repository at this point in the history
Signed-off-by: sh1luo <690898835@qq.com>
  • Loading branch information
sh1luo authored and hsluoyz committed Aug 19, 2021
1 parent e1182bb commit 75e917a
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 2 deletions.
230 changes: 230 additions & 0 deletions idp/gitlab.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions idp/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion object/sms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions object/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
1 change: 1 addition & 0 deletions web/src/ProviderEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
32 changes: 32 additions & 0 deletions web/src/auth/GitLabLoginButton.js
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions web/src/auth/LoginPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 9 additions & 1 deletion web/src/auth/Provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -82,6 +86,8 @@ export function getAuthLogo(provider) {
return WeComAuthLogo;
} else if (provider.type === "Lark") {
return LarkAuthLogo;
} else if (provider.type === "GitLab") {
return GitLabAuthLogo;
}
}

Expand Down Expand Up @@ -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}`
}
}

0 comments on commit 75e917a

Please sign in to comment.