This commit is contained in:
parent
b4a555e3da
commit
f0b1cefb72
|
|
@ -152,16 +152,20 @@ type LocalUser struct {
|
|||
}
|
||||
|
||||
type OAuth2Provider struct {
|
||||
Name string
|
||||
Title string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
Icon string
|
||||
Scopes []string
|
||||
AuthUrl string
|
||||
TokenUrl string
|
||||
WhoamiUrl string
|
||||
UsernameField string
|
||||
Name string
|
||||
Title string
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
Icon string
|
||||
Scopes []string
|
||||
AuthUrl string
|
||||
TokenUrl string
|
||||
WhoamiUrl string
|
||||
UsernameField string
|
||||
UserGroupField string
|
||||
InsecureSkipVerify bool
|
||||
CallbackTimeout int
|
||||
CertBundlePath string
|
||||
}
|
||||
|
||||
type NavigationLink struct {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package httpservers
|
|||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
|
@ -12,6 +14,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -93,7 +96,7 @@ func randString(nByte int) (string, error) {
|
|||
return base64.URLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func setOauthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
|
||||
func setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
|
|
@ -129,7 +132,7 @@ func handleOAuthLogin(w http.ResponseWriter, r *http.Request) {
|
|||
Username: "",
|
||||
}
|
||||
|
||||
setOauthCallbackCookie(w, r, "olivetin-sid-oauth", state)
|
||||
setOAuthCallbackCookie(w, r, "olivetin-sid-oauth", state)
|
||||
|
||||
log.Infof("OAuth2 state: %v mapped to provider %v (found: %v), now redirecting", state, providerName, provider != nil)
|
||||
|
||||
|
|
@ -165,6 +168,44 @@ func checkOAuthCallbackCookie(w http.ResponseWriter, r *http.Request) (*oauth2St
|
|||
return registeredState, state, true
|
||||
}
|
||||
|
||||
type HttpClientSettings struct {
|
||||
Transport *http.Transport
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
func getOAuth2HttpClient(providerConfig *config.OAuth2Provider) *HttpClientSettings {
|
||||
config := &HttpClientSettings{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: providerConfig.InsecureSkipVerify},
|
||||
},
|
||||
Timeout: time.Duration(min(3, providerConfig.CallbackTimeout)) * time.Second,
|
||||
}
|
||||
|
||||
if providerConfig.CertBundlePath != "" {
|
||||
config.Transport.TLSClientConfig.RootCAs = getOAuthCertBundle(providerConfig)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func getOAuthCertBundle(providerConfig *config.OAuth2Provider) *x509.CertPool {
|
||||
caCert, err := os.ReadFile(providerConfig.CertBundlePath)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("OAuth2 Cert Bundle - failed to read file: %v", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
|
||||
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
|
||||
log.Errorf("OAuth2 Cert Bundle - failed to append certificates: %v", err)
|
||||
}
|
||||
|
||||
return caCertPool
|
||||
}
|
||||
|
||||
func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
log.Infof("OAuth2 Callback received")
|
||||
|
||||
|
|
@ -181,9 +222,17 @@ func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
"token-code": code,
|
||||
}).Debug("OAuth2 Token Code")
|
||||
|
||||
httpClient := &http.Client{Timeout: 2 * time.Second}
|
||||
providerConfig := cfg.AuthOAuth2Providers[registeredState.providerName]
|
||||
|
||||
clientSettings := getOAuth2HttpClient(providerConfig)
|
||||
|
||||
exchangeClient := &http.Client{
|
||||
Transport: clientSettings.Transport,
|
||||
Timeout: clientSettings.Timeout,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
|
||||
ctx = context.WithValue(ctx, oauth2.HTTPClient, exchangeClient)
|
||||
|
||||
tok, err := registeredState.providerConfig.Exchange(ctx, code)
|
||||
|
||||
|
|
@ -193,11 +242,18 @@ func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
client := registeredState.providerConfig.Client(ctx, tok)
|
||||
userInfoClient := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: registeredState.providerConfig.TokenSource(ctx, tok),
|
||||
Base: clientSettings.Transport,
|
||||
},
|
||||
Timeout: clientSettings.Timeout,
|
||||
}
|
||||
|
||||
username := getUsername(client, cfg.AuthOAuth2Providers[registeredState.providerName])
|
||||
userinfo := getUserInfo(userInfoClient, cfg.AuthOAuth2Providers[registeredState.providerName])
|
||||
|
||||
registeredStates[state].Username = username
|
||||
registeredStates[state].Username = userinfo.Username
|
||||
registeredStates[state].Usergroup = userinfo.Usergroup
|
||||
|
||||
for k, v := range registeredStates {
|
||||
log.Debugf("states: %+v %+v", k, v)
|
||||
|
|
@ -213,12 +269,19 @@ func handleOAuthCallback(w http.ResponseWriter, r *http.Request) {
|
|||
w.Write([]byte(loginMessage))
|
||||
}
|
||||
|
||||
func getUsername(client *http.Client, provider *config.OAuth2Provider) string {
|
||||
type UserInfo struct {
|
||||
Username string
|
||||
Usergroup string
|
||||
}
|
||||
|
||||
func getUserInfo(client *http.Client, provider *config.OAuth2Provider) *UserInfo {
|
||||
ret := &UserInfo{}
|
||||
|
||||
res, err := client.Get(provider.WhoamiUrl)
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
log.Errorf("Failed to get user data: %v", res.StatusCode)
|
||||
return ""
|
||||
return ret
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
|
@ -232,18 +295,29 @@ func getUsername(client *http.Client, provider *config.OAuth2Provider) string {
|
|||
if err != nil {
|
||||
log.Errorf("Failed to unmarshal user data: %v", err)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
ret.Username = getDataField(userData, provider.UsernameField)
|
||||
ret.Usergroup = getDataField(userData, provider.UserGroupField)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getDataField(data map[string]interface{}, field string) string {
|
||||
if field == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
username, ok := userData[provider.UsernameField]
|
||||
val, ok := data[field]
|
||||
|
||||
if !ok {
|
||||
log.Errorf("Failed to get username from user data: %v", userData)
|
||||
log.Errorf("Failed to get field from user data: %v / %v", data, field)
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return username.(string)
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
func parseOAuth2Cookie(r *http.Request) (string, string, string) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue