fix(keycloak)

pull/1118/head
Diane LAKESTANI 2025-01-30 17:39:34 +01:00
parent 6dbfea4d2c
commit 7d2d3b418c
8 changed files with 144 additions and 125 deletions

View File

@ -168,6 +168,7 @@
<label for="switch-keycloak-oauth" class="mb-0"> </label> <label for="switch-keycloak-oauth" class="mb-0"> </label>
</span> </span>
</div> </div>
<div class="card-body" :class="{'d-none': !expanded.keycloak}">
<div class="form-group row"> <div class="form-group row">
<label for="keycloak_client_id" class="col-sm-4 col-form-label">Keycloak Client ID</label> <label for="keycloak_client_id" class="col-sm-4 col-form-label">Keycloak Client ID</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -181,21 +182,21 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="keycloak_auth_url" class="col-sm-4 col-form-label">Keycloak Auth URL</label> <label for="keycloak_endpoint_auth" class="col-sm-4 col-form-label">Keycloak Auth URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="oauth.keycloak_auth_url" type="text" class="form-control" id="keycloak_auth_url" required> <input v-model="oauth.keycloak_endpoint_auth" type="text" class="form-control" id="keycloak_endpoint_auth" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="keycloak_token_url" class="col-sm-4 col-form-label">Keycloak Token URL</label> <label for="keycloak_endpoint_token" class="col-sm-4 col-form-label">Keycloak Token URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="oauth.keycloak_token_url" type="text" class="form-control" id="keycloak_token_url" required> <input v-model="oauth.keycloak_endpoint_token" type="text" class="form-control" id="keycloak_endpoint_token" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="keycloak_user_info_url" class="col-sm-4 col-form-label">Keycloak User Info URL</label> <label for="keycloak_endpoint_userinfo" class="col-sm-4 col-form-label">Keycloak User Info URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="oauth.keycloak_user_info_url" type="text" class="form-control" id="keycloak_user_info_url" required> <input v-model="oauth.keycloak_endpoint_userinfo" type="text" class="form-control" id="keycloak_endpoint_userinfo" required>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -209,12 +210,6 @@
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="oauth.keycloak_admin_groups" type="text" class="form-control" id="keycloak_admin_groups" placeholder="e.g. group1,group2,group3,group4"> <input v-model="oauth.keycloak_admin_groups" type="text" class="form-control" id="keycloak_admin_groups" placeholder="e.g. group1,group2,group3,group4">
</div> </div>
</div>
<div class="form-group row">
<label for="keycloak_open_id" class="col-sm-4 col-form-label">Use OpenID</label>
<div class="col-sm-8">
<input v-model="oauth.keycloak_open_id" type="checkbox" class="form-check-input" id="keycloak_open_id">
</div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="switch-keycloak-open-id" class="col-sm-4 col-form-label">Open ID</label> <label for="switch-keycloak-open-id" class="col-sm-4 col-form-label">Open ID</label>
@ -226,15 +221,14 @@
<small>Enable if provider is OpenID</small> <small>Enable if provider is OpenID</small>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label> <label for="keycloak_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="input-group"> <div class="input-group">
<input v-bind:value="`${core.domain}/oauth/keycloak`" type="text" class="form-control" id="keycloak_callback" readonly> <input v-bind:value="`${core.domain}/oauth/keycloak`" type="text" class="form-control" id="keycloak_callback" readonly>
<div class="input-group-append copy-btn"> <div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/keycloak`)" class="btn btn-outline-secondary" type="button">Copy</button> <button @click.prevent="copy(`${core.domain}/oauth/keycloak`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -367,11 +361,11 @@
custom_endpoint_token: "", custom_endpoint_token: "",
custom_scopes: "", custom_scopes: "",
custom_open_id: false, custom_open_id: false,
keycloak_client: "", keycloak_client_id: "",
keycloak_client_secret: "", keycloak_client_secret: "",
keycloak_auth_url: "", keycloak_endpoint_auth: "",
keycloak_token_url: "", keycloak_endpoint_token: "",
keycloak_user_info_url: "", keycloak_endpoint_userinfo: "",
keycloak_scopes: '', keycloak_scopes: '',
keycloak_admin_groups: '', keycloak_admin_groups: '',
keycloak_open_id: false, keycloak_open_id: false,

View File

@ -54,30 +54,38 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) {
Id: 0, Id: 0,
Username: oauth.Username, Username: oauth.Username,
Email: oauth.Email, Email: oauth.Email,
Admin: null.NewNullBool(false), Admin: null.NewNullBool(true),
} }
log.Infof("OAuth User: %+v", user)
log.Infof("OAuth User Groups: %+v", oauth.Groups)
log.Infof("core.App.OAuth.KeycloakAdminGroups: %+v", core.App.OAuth.KeycloakAdminGroups)
isAdmin := false
// Check if the user is in the Keycloak admin groups // Check if the user is in the Keycloak admin groups
if oauth.Groups != nil && core.App.OAuth.KeycloakAdminGroups != "" { if oauth.Groups != nil && core.App.OAuth.KeycloakAdminGroups != "" {
adminGroups := strings.Split(core.App.OAuth.KeycloakAdminGroups, ",") adminGroups := strings.Split(core.App.OAuth.KeycloakAdminGroups, ",")
for _, group := range adminGroups { log.Infof("Admin Groups: %+v", adminGroups)
if contains(oauth.Groups, group) {
user.Admin = null.NewNullBool(true) for _, keycloakAdminGroup := range adminGroups {
for _, userGroup := range oauth.Groups {
log.Infof("Checking if user group '%s' is in admin group '%s'", userGroup, keycloakAdminGroup)
if userGroup == keycloakAdminGroup {
isAdmin = true // Set the flag to true if a group match is found
break
}
}
if user.Admin.Valid && user.Admin.Bool {
break break
} }
} }
} }
user.Admin = null.NewNullBool(isAdmin)
log.Infof("OAuth User Admin: %+v", user.Admin)
log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type(), oauth.Email, r.RemoteAddr)) log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type(), oauth.Email, r.RemoteAddr))
setJwtToken(user, w) setJwtToken(user, w)
http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect) http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect)
} }
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}

View File

@ -1,11 +1,13 @@
package handlers package handlers
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"strings" "strings"
"github.com/statping-ng/statping-ng/types/core" "github.com/statping-ng/statping-ng/types/core"
"github.com/statping-ng/statping-ng/types/errors"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -18,42 +20,57 @@ type keycloakUserInfo struct {
func keycloakOAuth(r *http.Request) (*oAuth, error) { func keycloakOAuth(r *http.Request) (*oAuth, error) {
auth := core.App.OAuth auth := core.App.OAuth
code := r.URL.Query().Get("code") code := r.URL.Query().Get("code")
log.Infof("Keycloak code: %s", code)
if code == "" {
return nil, errors.New("code not found")
}
scopes := strings.Split(auth.KeycloakScopes, ",")
config := &oauth2.Config{ conf := &oauth2.Config{
ClientID: auth.KeycloakClientID, ClientID: core.App.OAuth.KeycloakClientID,
ClientSecret: auth.KeycloakClientSecret, ClientSecret: core.App.OAuth.KeycloakClientSecret,
Endpoint: oauth2.Endpoint{ Endpoint: oauth2.Endpoint{
AuthURL: auth.KeycloakAuthURL, AuthURL: auth.KeycloakEndpointAuth,
TokenURL: auth.KeycloakTokenURL, TokenURL: auth.KeycloakEndpointToken,
}, },
RedirectURL: core.App.Domain + basePath + "oauth/keycloak", RedirectURL: core.App.Domain + basePath + "oauth/keycloak",
Scopes: strings.Split(auth.KeycloakScopes, ","), Scopes: scopes,
} }
log.Infof("Keycloak config: %+v", conf)
token, err := config.Exchange(r.Context(), code) token, err := conf.Exchange(context.Background(), code)
log.Infof("Keycloak token: %+v", token)
if err != nil { if err != nil {
log.Errorln("Error exchanging token:", err) log.Error("Failed to exchange token: ", err)
return nil, err return nil, err
} }
client := config.Client(r.Context(), token) client := conf.Client(context.Background(), token)
userInfoResp, err := client.Get(auth.KeycloakUserInfoURL) log.Infof("Keycloak client: %+v", client)
resp, err := client.Get(core.App.OAuth.KeycloakEndpointUserinfo)
log.Infof("Keycloak user info URL: %s", core.App.OAuth.KeycloakEndpointUserinfo)
if err != nil { if err != nil {
log.Errorln("Error getting user info:", err) log.Error("Failed to get user info: ", err)
return nil, err return nil, err
} }
defer userInfoResp.Body.Close() defer resp.Body.Close()
var user keycloakUserInfo var userInfo struct {
if err := json.NewDecoder(userInfoResp.Body).Decode(&user); err != nil { Email string `json:"email"`
log.Errorln("Error decoding user info:", err) Username string `json:"preferred_username"`
Groups []string `json:"groups"`
}
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
log.Error("Failed to decode user info: ", err)
return nil, err return nil, err
} }
log.Infof("Keycloak user info: %+v", userInfo)
log.Infof("Keycloak user groups: %+v", userInfo.Groups)
return &oAuth{ return &oAuth{
Email: userInfo.Email,
Username: userInfo.Username,
Token: token, Token: token,
Username: user.Username, Groups: userInfo.Groups,
Email: user.Email,
Groups: user.Groups,
}, nil }, nil
} }

View File

@ -28,9 +28,9 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
reports, _ := strconv.ParseBool(g("send_reports")) reports, _ := strconv.ParseBool(g("send_reports"))
keycloakClientID := g("keycloak_client_id") keycloakClientID := g("keycloak_client_id")
keycloakClientSecret := g("keycloak_client_secret") keycloakClientSecret := g("keycloak_client_secret")
keycloakAuthURL := g("keycloak_auth_url") keycloakEndpointAuth := g("keycloak_endpoint_auth")
keycloakTokenURL := g("keycloak_token_url") keycloakEndpointToken := g("keycloak_endpoint_token")
keycloakUserInfoURL := g("keycloak_user_info_url") keycloakEndpointUserinfo := g("keycloak_endpoint_userinfo")
if project == "" || username == "" || password == "" { if project == "" || username == "" || password == "" {
err := errors.New("Missing required elements on setup form") err := errors.New("Missing required elements on setup form")
@ -53,9 +53,9 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
p.Set("ADMIN_EMAIL", email) p.Set("ADMIN_EMAIL", email)
p.Set("KEYCLOAK_CLIENT_ID", keycloakClientID) p.Set("KEYCLOAK_CLIENT_ID", keycloakClientID)
p.Set("KEYCLOAK_CLIENT_SECRET", keycloakClientSecret) p.Set("KEYCLOAK_CLIENT_SECRET", keycloakClientSecret)
p.Set("KEYCLOAK_AUTH_URL", keycloakAuthURL) p.Set("KEYCLOAK_ENDPOINT_AUTH", keycloakEndpointAuth)
p.Set("KEYCLOAK_TOKEN_URL", keycloakTokenURL) p.Set("KEYCLOAK_ENDPOINT_TOKEN", keycloakEndpointToken)
p.Set("KEYCLOAK_USER_INFO_URL", keycloakUserInfoURL) p.Set("KEYCLOAK_ENDPOINT_USERINFO", keycloakEndpointUserinfo)
confg := &DbConfig{ confg := &DbConfig{
DbConn: dbConn, DbConn: dbConn,
@ -75,9 +75,9 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) {
AllowReports: reports, AllowReports: reports,
KeycloakClientID: keycloakClientID, KeycloakClientID: keycloakClientID,
KeycloakClientSecret: keycloakClientSecret, KeycloakClientSecret: keycloakClientSecret,
KeycloakAuthURL: keycloakAuthURL, KeycloakEndpointAuth: keycloakEndpointAuth,
KeycloakTokenURL: keycloakTokenURL, KeycloakEndpointToken: keycloakEndpointToken,
KeycloakUserInfoURL: keycloakUserInfoURL, KeycloakEndpointUserinfo: keycloakEndpointUserinfo,
} }
return confg, nil return confg, nil

View File

@ -36,9 +36,9 @@ func Save() error {
MaxLifeConnections: int(p.GetDuration("MAX_LIFE_CONN").Seconds()), MaxLifeConnections: int(p.GetDuration("MAX_LIFE_CONN").Seconds()),
KeycloakClientID: p.GetString("KEYCLOAK_CLIENT_ID"), KeycloakClientID: p.GetString("KEYCLOAK_CLIENT_ID"),
KeycloakClientSecret: p.GetString("KEYCLOAK_CLIENT_SECRET"), KeycloakClientSecret: p.GetString("KEYCLOAK_CLIENT_SECRET"),
KeycloakAuthURL: p.GetString("KEYCLOAK_AUTH_URL"), KeycloakEndpointAuth: p.GetString("KEYCLOAK_ENDPOINT_AUTH"),
KeycloakTokenURL: p.GetString("KEYCLOAK_TOKEN_URL"), KeycloakEndpointToken: p.GetString("KEYCLOAK_ENDPOINT_TOKEN"),
KeycloakUserInfoURL: p.GetString("KEYCLOAK_USER_INFO_URL"), KeycloakEndpointUserinfo: p.GetString("KEYCLOAK_ENDPOINT_USERINFO"),
KeycloakScopes: p.GetString("KEYCLOAK_SCOPES"), KeycloakScopes: p.GetString("KEYCLOAK_SCOPES"),
KeycloakAdminGroups: p.GetString("KEYCLOAK_ADMIN_GROUPS"), KeycloakAdminGroups: p.GetString("KEYCLOAK_ADMIN_GROUPS"),
} }
@ -115,14 +115,14 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
if db.KeycloakClientSecret != "" { if db.KeycloakClientSecret != "" {
p.Set("KEYCLOAK_CLIENT_SECRET", db.KeycloakClientSecret) p.Set("KEYCLOAK_CLIENT_SECRET", db.KeycloakClientSecret)
} }
if db.KeycloakAuthURL != "" { if db.KeycloakEndpointAuth != "" {
p.Set("KEYCLOAK_AUTH_URL", db.KeycloakAuthURL) p.Set("KEYCLOAK_ENDPOINT_AUTH", db.KeycloakEndpointAuth)
} }
if db.KeycloakTokenURL != "" { if db.KeycloakEndpointToken != "" {
p.Set("KEYCLOAK_TOKEN_URL", db.KeycloakTokenURL) p.Set("KEYCLOAK_ENDPOINT_TOKEN", db.KeycloakEndpointToken)
} }
if db.KeycloakUserInfoURL != "" { if db.KeycloakEndpointUserinfo != "" {
p.Set("KEYCLOAK_USER_INFO_URL", db.KeycloakUserInfoURL) p.Set("KEYCLOAK_ENDPOINT_USERINFO", db.KeycloakEndpointUserinfo)
} }
if db.KeycloakScopes != "" { if db.KeycloakScopes != "" {
p.Set("KEYCLOAK_SCOPES", db.KeycloakScopes) p.Set("KEYCLOAK_SCOPES", db.KeycloakScopes)
@ -155,9 +155,9 @@ func LoadConfigs(cfgFile string) (*DbConfig, error) {
SampleData: p.GetBool("SAMPLE_DATA"), SampleData: p.GetBool("SAMPLE_DATA"),
KeycloakClientID: p.GetString("KEYCLOAK_CLIENT_ID"), KeycloakClientID: p.GetString("KEYCLOAK_CLIENT_ID"),
KeycloakClientSecret: p.GetString("KEYCLOAK_CLIENT_SECRET"), KeycloakClientSecret: p.GetString("KEYCLOAK_CLIENT_SECRET"),
KeycloakAuthURL: p.GetString("KEYCLOAK_AUTH_URL"), KeycloakEndpointAuth: p.GetString("KEYCLOAK_ENDPOINT_AUTH"),
KeycloakTokenURL: p.GetString("KEYCLOAK_TOKEN_URL"), KeycloakEndpointToken: p.GetString("KEYCLOAK_ENDPOINT_TOKEN"),
KeycloakUserInfoURL: p.GetString("KEYCLOAK_USER_INFO_URL"), KeycloakEndpointUserinfo: p.GetString("KEYCLOAK_ENDPOINT_USERINFO"),
KeycloakScopes: p.GetString("KEYCLOAK_SCOPES"), KeycloakScopes: p.GetString("KEYCLOAK_SCOPES"),
KeycloakAdminGroups: p.GetString("KEYCLOAK_ADMIN_GROUPS"), KeycloakAdminGroups: p.GetString("KEYCLOAK_ADMIN_GROUPS"),
} }

View File

@ -51,9 +51,9 @@ type DbConfig struct {
KeycloakClientID string `yaml:"keycloak_client_id,omitempty" json:"keycloak_client_id"` KeycloakClientID string `yaml:"keycloak_client_id,omitempty" json:"keycloak_client_id"`
KeycloakClientSecret string `yaml:"keycloak_client_secret,omitempty" json:"keycloak_client_secret"` KeycloakClientSecret string `yaml:"keycloak_client_secret,omitempty" json:"keycloak_client_secret"`
KeycloakAuthURL string `yaml:"keycloak_auth_url,omitempty" json:"keycloak_auth_url"` KeycloakEndpointAuth string `yaml:"keycloak_endpoint_auth,omitempty" json:"keycloak_endpoint_auth"`
KeycloakTokenURL string `yaml:"keycloak_token_url,omitempty" json:"keycloak_token_url"` KeycloakEndpointToken string `yaml:"keycloak_endpoint_token,omitempty" json:"keycloak_endpoint_token"`
KeycloakUserInfoURL string `yaml:"keycloak_user_info_url,omitempty" json:"keycloak_user_info_url"` KeycloakEndpointUserinfo string `yaml:"keycloak_endpoint_userinfo,omitempty" json:"keycloak_endpoint_userinfo"`
KeycloakScopes string `yaml:"keycloak_scopes,omitempty" json:"keycloak_scopes"` KeycloakScopes string `yaml:"keycloak_scopes,omitempty" json:"keycloak_scopes"`
KeycloakAdminGroups string `yaml:"keycloak_admin_groups,omitempty" json:"keycloak_admin_groups"` KeycloakAdminGroups string `yaml:"keycloak_admin_groups,omitempty" json:"keycloak_admin_groups"`
KeycloakIsOpenID bool `yaml:"keycloak_open_id,omitempty" json:"keycloak_open_id"` KeycloakIsOpenID bool `yaml:"keycloak_open_id,omitempty" json:"keycloak_open_id"`

View File

@ -66,9 +66,9 @@ type OAuth struct {
CustomIsOpenID null.NullBool `gorm:"column:custom_open_id" json:"custom_open_id"` CustomIsOpenID null.NullBool `gorm:"column:custom_open_id" json:"custom_open_id"`
KeycloakClientID string `gorm:"column:keycloak_client_id" json:"keycloak_client_id"` KeycloakClientID string `gorm:"column:keycloak_client_id" json:"keycloak_client_id"`
KeycloakClientSecret string `gorm:"column:keycloak_client_secret" json:"keycloak_client_secret" scope:"admin"` KeycloakClientSecret string `gorm:"column:keycloak_client_secret" json:"keycloak_client_secret" scope:"admin"`
KeycloakAuthURL string `gorm:"column:keycloak_auth_url" json:"keycloak_auth_url"` KeycloakEndpointAuth string `gorm:"column:keycloak_endpoint_auth" json:"keycloak_endpoint_auth"`
KeycloakTokenURL string `gorm:"column:keycloak_token_url" json:"keycloak_token_url"` KeycloakEndpointToken string `gorm:"column:keycloak_endpoint_token" json:"keycloak_endpoint_token"`
KeycloakUserInfoURL string `gorm:"column:keycloak_user_info_url" json:"keycloak_user_info_url"` KeycloakEndpointUserinfo string `gorm:"column:keycloak_endpoint_userinfo" json:"keycloak_endpoint_userinfo"`
KeycloakScopes string `gorm:"column:keycloak_scopes" json:"keycloak_scopes"` KeycloakScopes string `gorm:"column:keycloak_scopes" json:"keycloak_scopes"`
KeycloakAdminGroups string `gorm:"column:keycloak_admin_groups" json:"keycloak_admin_groups"` KeycloakAdminGroups string `gorm:"column:keycloak_admin_groups" json:"keycloak_admin_groups"`
KeycloakIsOpenID null.NullBool `gorm:"column:keycloak_open_id" json:"keycloak_open_id"` KeycloakIsOpenID null.NullBool `gorm:"column:keycloak_open_id" json:"keycloak_open_id"`

View File

@ -61,9 +61,9 @@ func InitEnvs() {
Params.SetDefault("DISABLE_COLORS", false) Params.SetDefault("DISABLE_COLORS", false)
Params.SetDefault("KEYCLOAK_CLIENT_ID", "") Params.SetDefault("KEYCLOAK_CLIENT_ID", "")
Params.SetDefault("KEYCLOAK_CLIENT_SECRET", "") Params.SetDefault("KEYCLOAK_CLIENT_SECRET", "")
Params.SetDefault("KEYCLOAK_AUTH_URL", "") Params.SetDefault("KEYCLOAK_ENDPOINT_AUTH", "")
Params.SetDefault("KEYCLOAK_TOKEN_URL", "") Params.SetDefault("KEYCLOAK_ENDPOINT_TOKEN", "")
Params.SetDefault("KEYCLOAK_USER_INFO_URL", "") Params.SetDefault("KEYCLOAK_ENDPOINT_USERINFO", "")
Params.SetDefault("KEYCLOAK_SCOPES", "openid profile email") Params.SetDefault("KEYCLOAK_SCOPES", "openid profile email")
Params.SetDefault("KEYCLOAK_ADMIN_GROUPS", "") Params.SetDefault("KEYCLOAK_ADMIN_GROUPS", "")
Params.SetDefault("KEYCLOAK_OPENID", false) Params.SetDefault("KEYCLOAK_OPENID", false)