1package service
2
3import (
4	"encoding/base64"
5	"fmt"
6	"strings"
7	"time"
8
9	goidentity "github.com/jcmturner/goidentity/v6"
10	"github.com/jcmturner/gokrb5/v8/client"
11	"github.com/jcmturner/gokrb5/v8/config"
12	"github.com/jcmturner/gokrb5/v8/credentials"
13)
14
15// NewKRB5BasicAuthenticator creates a new NewKRB5BasicAuthenticator
16func NewKRB5BasicAuthenticator(headerVal string, krb5conf *config.Config, serviceSettings *Settings, clientSettings *client.Settings) KRB5BasicAuthenticator {
17	return KRB5BasicAuthenticator{
18		BasicHeaderValue: headerVal,
19		clientConfig:     krb5conf,
20		serviceSettings:  serviceSettings,
21		clientSettings:   clientSettings,
22	}
23}
24
25// KRB5BasicAuthenticator implements gokrb5.com/jcmturner/goidentity.Authenticator interface.
26// It takes username and password so can be used for basic authentication.
27type KRB5BasicAuthenticator struct {
28	BasicHeaderValue string
29	serviceSettings  *Settings
30	clientSettings   *client.Settings
31	clientConfig     *config.Config
32	realm            string
33	username         string
34	password         string
35}
36
37// Authenticate and return the identity. The boolean indicates if the authentication was successful.
38func (a KRB5BasicAuthenticator) Authenticate() (i goidentity.Identity, ok bool, err error) {
39	a.realm, a.username, a.password, err = parseBasicHeaderValue(a.BasicHeaderValue)
40	if err != nil {
41		err = fmt.Errorf("could not parse basic authentication header: %v", err)
42		return
43	}
44	cl := client.NewWithPassword(a.username, a.realm, a.password, a.clientConfig)
45	err = cl.Login()
46	if err != nil {
47		// Username and/or password could be wrong
48		err = fmt.Errorf("error with user credentials during login: %v", err)
49		return
50	}
51	tkt, _, err := cl.GetServiceTicket(a.serviceSettings.SName())
52	if err != nil {
53		err = fmt.Errorf("could not get service ticket: %v", err)
54		return
55	}
56	err = tkt.DecryptEncPart(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal())
57	if err != nil {
58		err = fmt.Errorf("could not decrypt service ticket: %v", err)
59		return
60	}
61	cl.Credentials.SetAuthTime(time.Now().UTC())
62	cl.Credentials.SetAuthenticated(true)
63	isPAC, pac, err := tkt.GetPACType(a.serviceSettings.Keytab, a.serviceSettings.KeytabPrincipal(), a.serviceSettings.Logger())
64	if isPAC && err != nil {
65		err = fmt.Errorf("error processing PAC: %v", err)
66		return
67	}
68	if isPAC {
69		// There is a valid PAC. Adding attributes to creds
70		cl.Credentials.SetADCredentials(credentials.ADCredentials{
71			GroupMembershipSIDs: pac.KerbValidationInfo.GetGroupMembershipSIDs(),
72			LogOnTime:           pac.KerbValidationInfo.LogOnTime.Time(),
73			LogOffTime:          pac.KerbValidationInfo.LogOffTime.Time(),
74			PasswordLastSet:     pac.KerbValidationInfo.PasswordLastSet.Time(),
75			EffectiveName:       pac.KerbValidationInfo.EffectiveName.Value,
76			FullName:            pac.KerbValidationInfo.FullName.Value,
77			UserID:              int(pac.KerbValidationInfo.UserID),
78			PrimaryGroupID:      int(pac.KerbValidationInfo.PrimaryGroupID),
79			LogonServer:         pac.KerbValidationInfo.LogonServer.Value,
80			LogonDomainName:     pac.KerbValidationInfo.LogonDomainName.Value,
81			LogonDomainID:       pac.KerbValidationInfo.LogonDomainID.String(),
82		})
83	}
84	ok = true
85	i = cl.Credentials
86	return
87}
88
89// Mechanism returns the authentication mechanism.
90func (a KRB5BasicAuthenticator) Mechanism() string {
91	return "Kerberos Basic"
92}
93
94func parseBasicHeaderValue(s string) (domain, username, password string, err error) {
95	b, err := base64.StdEncoding.DecodeString(s)
96	if err != nil {
97		return
98	}
99	v := string(b)
100	vc := strings.SplitN(v, ":", 2)
101	password = vc[1]
102	// Domain and username can be specified in 2 formats:
103	// <Username> - no domain specified
104	// <Domain>\<Username>
105	// <Username>@<Domain>
106	if strings.Contains(vc[0], `\`) {
107		u := strings.SplitN(vc[0], `\`, 2)
108		domain = u[0]
109		username = u[1]
110	} else if strings.Contains(vc[0], `@`) {
111		u := strings.SplitN(vc[0], `@`, 2)
112		domain = u[1]
113		username = u[0]
114	} else {
115		username = vc[0]
116	}
117	return
118}
119