forked from casdoor/casdoor
110 lines
3.7 KiB
Go
110 lines
3.7 KiB
Go
// Copyright 2026 The Casdoor 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 object
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jcmturner/gokrb5/v8/credentials"
|
|
"github.com/jcmturner/gokrb5/v8/gssapi"
|
|
"github.com/jcmturner/gokrb5/v8/keytab"
|
|
"github.com/jcmturner/gokrb5/v8/service"
|
|
"github.com/jcmturner/gokrb5/v8/spnego"
|
|
)
|
|
|
|
// ctxCredentials is the SPNEGO context key holding the Kerberos credentials.
|
|
// This must match the value used internally by gokrb5's spnego package.
|
|
// If the gokrb5 library changes this internal constant in a future version,
|
|
// this value will need to be updated accordingly.
|
|
const ctxCredentials = "github.com/jcmturner/gokrb5/v8/ctxCredentials"
|
|
|
|
// ValidateKerberosToken validates a base64-encoded SPNEGO token from the
|
|
// Authorization header and returns the authenticated Kerberos username.
|
|
func ValidateKerberosToken(organization *Organization, spnegoTokenBase64 string) (string, error) {
|
|
if organization.KerberosRealm == "" || organization.KerberosKdcHost == "" || organization.KerberosKeytab == "" {
|
|
return "", fmt.Errorf("kerberos configuration is incomplete for organization: %s", organization.Name)
|
|
}
|
|
|
|
keytabData, err := base64.StdEncoding.DecodeString(organization.KerberosKeytab)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to decode keytab: %w", err)
|
|
}
|
|
|
|
kt := keytab.New()
|
|
err = kt.Unmarshal(keytabData)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to parse keytab: %w", err)
|
|
}
|
|
|
|
servicePrincipal := organization.KerberosServiceName
|
|
if servicePrincipal == "" {
|
|
servicePrincipal = "HTTP"
|
|
}
|
|
|
|
spnegoSvc := spnego.SPNEGOService(kt, service.KeytabPrincipal(servicePrincipal))
|
|
|
|
tokenBytes, err := base64.StdEncoding.DecodeString(spnegoTokenBase64)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to decode SPNEGO token: %w", err)
|
|
}
|
|
|
|
var st spnego.SPNEGOToken
|
|
err = st.Unmarshal(tokenBytes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to unmarshal SPNEGO token: %w", err)
|
|
}
|
|
|
|
authed, ctx, status := spnegoSvc.AcceptSecContext(&st)
|
|
if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded {
|
|
return "", fmt.Errorf("SPNEGO validation error: %s", status.Message)
|
|
}
|
|
if status.Code == gssapi.StatusContinueNeeded {
|
|
return "", fmt.Errorf("SPNEGO negotiation requires continuation, which is not supported")
|
|
}
|
|
if !authed {
|
|
return "", fmt.Errorf("SPNEGO token validation failed")
|
|
}
|
|
|
|
creds, ok := ctx.Value(ctxCredentials).(*credentials.Credentials)
|
|
if !ok || creds == nil {
|
|
return "", fmt.Errorf("no credentials found in SPNEGO context")
|
|
}
|
|
|
|
username := creds.UserName()
|
|
if username == "" {
|
|
return "", fmt.Errorf("no username found in Kerberos ticket")
|
|
}
|
|
|
|
return username, nil
|
|
}
|
|
|
|
// GetUserByKerberosName looks up a Casdoor user by their Kerberos principal name.
|
|
// It strips the realm part (e.g., "user@REALM.COM" -> "user") and searches by username.
|
|
func GetUserByKerberosName(organizationName string, kerberosUsername string) (*User, error) {
|
|
username := kerberosUsername
|
|
if idx := strings.Index(username, "@"); idx >= 0 {
|
|
username = username[:idx]
|
|
}
|
|
|
|
user, err := GetUserByFields(organizationName, username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|