2022-02-13 23:39:27 +08:00
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
2021-12-10 00:55:27 +08:00
//
// 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 (
"fmt"
"net/http"
"net/url"
"reflect"
"time"
2022-01-20 14:11:46 +08:00
"github.com/casdoor/casdoor/util"
2022-04-04 15:58:51 +08:00
"github.com/casdoor/goth"
"github.com/casdoor/goth/providers/amazon"
"github.com/casdoor/goth/providers/apple"
"github.com/casdoor/goth/providers/azuread"
"github.com/casdoor/goth/providers/bitbucket"
"github.com/casdoor/goth/providers/digitalocean"
"github.com/casdoor/goth/providers/discord"
"github.com/casdoor/goth/providers/dropbox"
"github.com/casdoor/goth/providers/facebook"
"github.com/casdoor/goth/providers/gitea"
"github.com/casdoor/goth/providers/github"
"github.com/casdoor/goth/providers/gitlab"
"github.com/casdoor/goth/providers/google"
"github.com/casdoor/goth/providers/heroku"
"github.com/casdoor/goth/providers/instagram"
"github.com/casdoor/goth/providers/kakao"
"github.com/casdoor/goth/providers/line"
"github.com/casdoor/goth/providers/linkedin"
"github.com/casdoor/goth/providers/microsoftonline"
"github.com/casdoor/goth/providers/paypal"
"github.com/casdoor/goth/providers/salesforce"
"github.com/casdoor/goth/providers/shopify"
"github.com/casdoor/goth/providers/slack"
"github.com/casdoor/goth/providers/steam"
"github.com/casdoor/goth/providers/tumblr"
"github.com/casdoor/goth/providers/twitter"
"github.com/casdoor/goth/providers/yahoo"
"github.com/casdoor/goth/providers/yandex"
"github.com/casdoor/goth/providers/zoom"
2021-12-10 00:55:27 +08:00
"golang.org/x/oauth2"
)
type GothIdProvider struct {
Provider goth . Provider
Session goth . Session
}
func NewGothIdProvider ( providerType string , clientId string , clientSecret string , redirectUrl string ) * GothIdProvider {
var idp GothIdProvider
switch providerType {
case "Amazon" :
idp = GothIdProvider {
Provider : amazon . New ( clientId , clientSecret , redirectUrl ) ,
Session : & amazon . Session { } ,
}
case "Apple" :
idp = GothIdProvider {
Provider : apple . New ( clientId , clientSecret , redirectUrl , nil ) ,
Session : & apple . Session { } ,
}
case "AzureAD" :
idp = GothIdProvider {
Provider : azuread . New ( clientId , clientSecret , redirectUrl , nil ) ,
Session : & azuread . Session { } ,
}
case "Bitbucket" :
idp = GothIdProvider {
Provider : bitbucket . New ( clientId , clientSecret , redirectUrl ) ,
Session : & bitbucket . Session { } ,
}
case "DigitalOcean" :
idp = GothIdProvider {
Provider : digitalocean . New ( clientId , clientSecret , redirectUrl ) ,
Session : & digitalocean . Session { } ,
}
case "Discord" :
idp = GothIdProvider {
Provider : discord . New ( clientId , clientSecret , redirectUrl ) ,
Session : & discord . Session { } ,
}
case "Dropbox" :
idp = GothIdProvider {
Provider : dropbox . New ( clientId , clientSecret , redirectUrl ) ,
Session : & dropbox . Session { } ,
}
case "Facebook" :
idp = GothIdProvider {
Provider : facebook . New ( clientId , clientSecret , redirectUrl ) ,
Session : & facebook . Session { } ,
}
case "Gitea" :
idp = GothIdProvider {
Provider : gitea . New ( clientId , clientSecret , redirectUrl ) ,
Session : & gitea . Session { } ,
}
case "GitHub" :
idp = GothIdProvider {
Provider : github . New ( clientId , clientSecret , redirectUrl ) ,
Session : & github . Session { } ,
}
case "GitLab" :
idp = GothIdProvider {
Provider : gitlab . New ( clientId , clientSecret , redirectUrl ) ,
Session : & gitlab . Session { } ,
}
case "Google" :
idp = GothIdProvider {
Provider : google . New ( clientId , clientSecret , redirectUrl ) ,
Session : & google . Session { } ,
}
case "Heroku" :
idp = GothIdProvider {
Provider : heroku . New ( clientId , clientSecret , redirectUrl ) ,
Session : & heroku . Session { } ,
}
case "Instagram" :
idp = GothIdProvider {
Provider : instagram . New ( clientId , clientSecret , redirectUrl ) ,
Session : & instagram . Session { } ,
}
case "Kakao" :
idp = GothIdProvider {
Provider : kakao . New ( clientId , clientSecret , redirectUrl ) ,
Session : & kakao . Session { } ,
}
case "Linkedin" :
idp = GothIdProvider {
Provider : linkedin . New ( clientId , clientSecret , redirectUrl ) ,
Session : & linkedin . Session { } ,
}
case "Line" :
idp = GothIdProvider {
Provider : line . New ( clientId , clientSecret , redirectUrl ) ,
Session : & line . Session { } ,
}
case "MicrosoftOnline" :
idp = GothIdProvider {
Provider : microsoftonline . New ( clientId , clientSecret , redirectUrl ) ,
Session : & microsoftonline . Session { } ,
}
case "Paypal" :
idp = GothIdProvider {
Provider : paypal . New ( clientId , clientSecret , redirectUrl ) ,
Session : & paypal . Session { } ,
}
case "SalesForce" :
idp = GothIdProvider {
Provider : salesforce . New ( clientId , clientSecret , redirectUrl ) ,
Session : & salesforce . Session { } ,
}
case "Shopify" :
idp = GothIdProvider {
Provider : shopify . New ( clientId , clientSecret , redirectUrl ) ,
Session : & shopify . Session { } ,
}
case "Slack" :
idp = GothIdProvider {
Provider : slack . New ( clientId , clientSecret , redirectUrl ) ,
Session : & slack . Session { } ,
}
2022-02-16 19:57:46 +08:00
case "Steam" :
idp = GothIdProvider {
Provider : steam . New ( clientSecret , redirectUrl ) ,
Session : & steam . Session { } ,
}
2021-12-10 00:55:27 +08:00
case "Tumblr" :
idp = GothIdProvider {
Provider : tumblr . New ( clientId , clientSecret , redirectUrl ) ,
Session : & tumblr . Session { } ,
}
case "Twitter" :
idp = GothIdProvider {
Provider : twitter . New ( clientId , clientSecret , redirectUrl ) ,
Session : & twitter . Session { } ,
}
case "Yahoo" :
idp = GothIdProvider {
Provider : yahoo . New ( clientId , clientSecret , redirectUrl ) ,
Session : & yahoo . Session { } ,
}
case "Yandex" :
idp = GothIdProvider {
Provider : yandex . New ( clientId , clientSecret , redirectUrl ) ,
Session : & yandex . Session { } ,
}
case "Zoom" :
idp = GothIdProvider {
Provider : zoom . New ( clientId , clientSecret , redirectUrl ) ,
Session : & zoom . Session { } ,
}
}
return & idp
}
2022-08-07 12:26:14 +08:00
// Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
2021-12-10 00:55:27 +08:00
func ( idp * GothIdProvider ) SetHttpClient ( client * http . Client ) {
idpClient := reflect . ValueOf ( idp . Provider ) . Elem ( ) . FieldByName ( "HTTPClient" )
idpClient . Set ( reflect . ValueOf ( client ) )
}
func ( idp * GothIdProvider ) GetToken ( code string ) ( * oauth2 . Token , error ) {
var expireAt time . Time
2022-02-16 19:57:46 +08:00
var value url . Values
var err error
if idp . Provider . Name ( ) == "steam" {
value , err = url . ParseQuery ( code )
returnUrl := reflect . ValueOf ( idp . Session ) . Elem ( ) . FieldByName ( "CallbackURL" )
returnUrl . Set ( reflect . ValueOf ( value . Get ( "openid.return_to" ) ) )
if err != nil {
return nil , err
}
} else {
2022-08-07 12:26:14 +08:00
// Need to construct variables supported by goth
// to call the function to obtain accessToken
2022-02-16 19:57:46 +08:00
value = url . Values { }
value . Add ( "code" , code )
}
2021-12-10 00:55:27 +08:00
accessToken , err := idp . Session . Authorize ( idp . Provider , value )
2022-03-26 15:15:56 +08:00
if err != nil {
return nil , err
}
2022-08-07 12:26:14 +08:00
// Get ExpiresAt's value
2021-12-10 00:55:27 +08:00
valueOfExpire := reflect . ValueOf ( idp . Session ) . Elem ( ) . FieldByName ( "ExpiresAt" )
if valueOfExpire . IsValid ( ) {
expireAt = valueOfExpire . Interface ( ) . ( time . Time )
}
token := oauth2 . Token {
AccessToken : accessToken ,
Expiry : expireAt ,
}
2022-03-26 15:15:56 +08:00
return & token , nil
2021-12-10 00:55:27 +08:00
}
func ( idp * GothIdProvider ) GetUserInfo ( token * oauth2 . Token ) ( * UserInfo , error ) {
gothUser , err := idp . Provider . FetchUser ( idp . Session )
if err != nil {
return nil , err
}
2022-02-16 19:57:46 +08:00
return getUser ( gothUser , idp . Provider . Name ( ) ) , nil
2021-12-10 00:55:27 +08:00
}
2022-02-16 19:57:46 +08:00
func getUser ( gothUser goth . User , provider string ) * UserInfo {
2021-12-10 00:55:27 +08:00
user := UserInfo {
Id : gothUser . UserID ,
Username : gothUser . Name ,
DisplayName : gothUser . NickName ,
Email : gothUser . Email ,
AvatarUrl : gothUser . AvatarURL ,
}
2022-08-07 12:26:14 +08:00
// Some idp return an empty Name
// so construct the Name with firstname and lastname or nickname
2021-12-10 00:55:27 +08:00
if user . Username == "" {
2021-12-27 18:55:25 +08:00
if gothUser . FirstName != "" && gothUser . LastName != "" {
user . Username = getName ( gothUser . FirstName , gothUser . LastName )
} else {
user . Username = gothUser . NickName
}
2021-12-10 00:55:27 +08:00
}
2021-12-27 18:55:25 +08:00
if user . DisplayName == "" {
if gothUser . FirstName != "" && gothUser . LastName != "" {
user . DisplayName = getName ( gothUser . FirstName , gothUser . LastName )
} else {
user . DisplayName = user . Username
}
2021-12-10 00:55:27 +08:00
}
2022-02-16 19:57:46 +08:00
if provider == "steam" {
user . Username = user . DisplayName
user . Email = ""
}
2021-12-10 00:55:27 +08:00
return & user
}
2021-12-27 18:55:25 +08:00
func getName ( firstName , lastName string ) string {
if util . IsChinese ( firstName ) || util . IsChinese ( lastName ) {
return fmt . Sprintf ( "%s%s" , lastName , firstName )
} else {
return fmt . Sprintf ( "%s %s" , firstName , lastName )
}
}