diff --git a/doc.go b/doc.go
new file mode 100644
index 0000000..27d18ce
--- /dev/null
+++ b/doc.go
@@ -0,0 +1,4 @@
+/*
+Package websspi provides middleware to require with Windows Integrated Authentication.
+*/
+package websspi
diff --git a/examples/server_windows.go b/examples/server_windows.go
index f83c7d1..54a546e 100644
--- a/examples/server_windows.go
+++ b/examples/server_windows.go
@@ -14,16 +14,25 @@ import (
)
var helloTemplate = template.Must(template.New("index.html").Parse(`
-{{- if . -}}
-
Hello {{ .Username }}!
+{{- if .User -}}
+Hello {{ .User.Username }}!
-{{ if .Groups -}}
+{{ if .User.Groups -}}
Groups:
-{{- range .Groups}}
+{{- range .User.Groups}}
- {{ . }}
{{end -}}
+{{- if .Linked}}
+Linked Token: {{ .Linked.Username }}
+Groups:
+
+{{- range .Linked.Groups}}
+ - {{ . }}
+{{end -}}
+
+{{end -}}
{{- end }}
{{- else -}}
Hello!
@@ -34,6 +43,10 @@ func main() {
config := websspi.NewConfig()
config.EnumerateGroups = true // If groups should be resolved
// config.ServerName = "..." // If static instead of dynamic group membership should be resolved
+ config.ResolveLinked = true
+ // If a linked token should be resolved.
+ // For UAC restricted admin the linked user info will have the "all" groups.
+ // For UAC elevated user the linked user info will have the restricted ones.
auth, err := websspi.New(config)
if err != nil {
@@ -43,9 +56,16 @@ func main() {
server := &http.Server{Addr: "0.0.0.0:9000"}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
info := r.Context().Value(websspi.UserInfoKey)
+ linked := r.Context().Value(websspi.LinkedTokenUserInfoKey)
userInfo, _ := info.(*websspi.UserInfo)
+ linkedTokenUserInfo, _ := linked.(*websspi.UserInfo)
w.Header().Add("Content-Type", "text/html; encoding=utf-8")
- helloTemplate.Execute(w, userInfo)
+ helloTemplate.Execute(w, struct {
+ User *websspi.UserInfo
+ Linked *websspi.UserInfo
+ }{
+ userInfo, linkedTokenUserInfo,
+ })
})
http.Handle("/", auth.WithAuth(handler))
diff --git a/userinfo.go b/userinfo.go
index 359bfb5..0f5d465 100644
--- a/userinfo.go
+++ b/userinfo.go
@@ -4,4 +4,6 @@ package websspi
type UserInfo struct {
Username string // Name of user, usually in the form DOMAIN\User
Groups []string // The global groups the user is a member of
+
+ linked *UserInfo
}
diff --git a/websspi_test.go b/websspi_test.go
index f80fe6b..c3c5163 100644
--- a/websspi_test.go
+++ b/websspi_test.go
@@ -10,6 +10,7 @@ import (
"net/http/httptest"
"os/user"
"reflect"
+ "sort"
"strings"
"syscall"
"testing"
@@ -23,8 +24,16 @@ var sidRemoteDesktopUsers *syscall.SID
var resolvedGroups []string
var resolvedGroupsWoAdmin []string
+var sidThisUser *syscall.SID
+var thisUser string
+
func init() {
+ me, _ := user.Current()
+ normalized, _ := user.LookupId(me.Uid)
+ thisUser = normalized.Username
+
for stringSid, binPtr := range map[string]**syscall.SID{
+ me.Uid: &sidThisUser, // ...\Administrator
"S-1-5-32-544": &sidAdministrators, // BUILTIN\Administrators
"S-1-5-32-545": &sidUsers, // BUILTIN\Users
"S-1-5-32-555": &sidRemoteDesktopUsers, // BUILTIN\Remote Desktop Users
@@ -156,7 +165,7 @@ func (s *stubAPI) GetTokenInformation(t syscall.Token, infoClass uint32, info *b
temp2, ok := temp1[int(infoClass)]
if !ok {
- return syscall.Errno(998)
+ return syscall.Errno(999)
}
length := len(temp2)
@@ -244,6 +253,38 @@ func newGroups(limited bool) []byte {
return out
}
+func newUser() []byte {
+ u := TokenUser{
+ syscall.SIDAndAttributes{
+ Sid: sidThisUser,
+ Attributes: 0,
+ },
+ }
+
+ in := make([]byte, reflect.TypeOf(u).Size())
+ out := make([]byte, reflect.TypeOf(u).Size())
+ var inHdr *reflect.SliceHeader
+ inHdr = (*reflect.SliceHeader)(unsafe.Pointer(&in))
+ inHdr.Data = uintptr(unsafe.Pointer(&u))
+
+ copy(out, in)
+ return out
+}
+
+func newToken() []byte {
+ u := TokenLinkedToken{
+ LinkedToken: 2,
+ }
+ in := make([]byte, reflect.TypeOf(u).Size())
+ out := make([]byte, reflect.TypeOf(u).Size())
+ var inHdr *reflect.SliceHeader
+ inHdr = (*reflect.SliceHeader)(unsafe.Pointer(&in))
+ inHdr.Data = uintptr(unsafe.Pointer(&u))
+
+ copy(out, in)
+ return out
+}
+
// newTestAuthenticator creates an Authenticator for use in tests.
func newTestAuthenticator(t *testing.T) *Authenticator {
entries, total, groupsBuf := newGroupUsersInfo0([]string{"group1", "group2", "group3"})
@@ -269,10 +310,12 @@ func newTestAuthenticator(t *testing.T) *Authenticator {
getTokenInformation: map[int]map[int][]byte{
1: {
- syscall.TokenGroups: newGroups(true),
+ syscall.TokenGroups: newGroups(true),
+ syscall.TokenLinkedToken: newToken(),
},
2: {
syscall.TokenGroups: newGroups(false),
+ syscall.TokenUser: newUser(),
},
},
},
@@ -642,6 +685,32 @@ func TestGetUserGroups_PartialRead(t *testing.T) {
}
}
+func TestGetLinkedUserInfo(t *testing.T) {
+ token1 := SecPkgContext_AccessToken{1}
+
+ auth := newTestAuthenticator(t)
+ auth.Config.ServerName = ""
+ auth.Config.authAPI.(*stubAPI).queryStatus = 0
+ auth.Config.authAPI.(*stubAPI).queryOutBuf = (*byte)(unsafe.Pointer(&token1))
+
+ linked, err := auth.GetLinkedUserInfo(nil)
+ if err != nil {
+ t.Fatal("GetLinkedUserInfo() returns an error.", err)
+ }
+
+ if linked.Username != thisUser {
+ t.Fatal("GetLinkedUserInfo() returns the wrong user", linked.Username, "instead of", thisUser)
+ }
+
+ expectedGroups := resolvedGroups
+ sort.Strings(linked.Groups)
+ sort.Strings(expectedGroups)
+
+ if len(linked.Groups) != len(expectedGroups) || !reflect.DeepEqual(linked.Groups, expectedGroups) {
+ t.Fatal("GetLinkedUserInfo() returns the wrong groups", linked.Groups, "instead of", expectedGroups)
+ }
+}
+
func TestGetGroups(t *testing.T) {
token1 := SecPkgContext_AccessToken{1}
diff --git a/websspi_windows.go b/websspi_windows.go
index b1a9df3..7c70728 100644
--- a/websspi_windows.go
+++ b/websspi_windows.go
@@ -63,7 +63,8 @@ func (c contextKey) String() string {
}
var (
- UserInfoKey = contextKey("UserInfo")
+ UserInfoKey = contextKey("UserInfo")
+ LinkedTokenUserInfoKey = contextKey("LinkedTokenUserInfo")
)
// The Authenticator type provides middleware methods for authentication of http requests.
@@ -341,19 +342,113 @@ func (a *Authenticator) GetUsername(context *CtxtHandle) (username string, err e
return
}
-// GetGroups returns the groups assosiated with the specified security context
-func (a *Authenticator) GetGroups(context *CtxtHandle) (groups []string, err error) {
+// GetAccessToken returns the access token of a context handle.
+func (a *Authenticator) GetAccessToken(context *CtxtHandle) (t syscall.Token, err error) {
var token SecPkgContext_AccessToken
status := a.Config.authAPI.QueryContextAttributes(context, SECPKG_ATTR_ACCESS_TOKEN, (*byte)(unsafe.Pointer(&token)))
if status != SEC_E_OK {
err = fmt.Errorf("QueryContextAttributes failed with status 0x%x", status)
return
}
+ return syscall.Token(token.AccessToken), err
+}
+
+// GetLinkedUserInfo returns the user info of a linked token e.g. the full token when using the UAC
+func (a *Authenticator) GetLinkedUserInfo(context *CtxtHandle) (u *UserInfo, err error) {
+ var token syscall.Token
+ token, err = a.GetAccessToken(context)
+ if err != nil {
+ return
+ }
+
+ linkedUserInfo := TokenLinkedToken{}
+ var usedMemory uint32
+
+ err = a.Config.authAPI.GetTokenInformation(
+ token,
+ uint32(syscall.TokenLinkedToken),
+ (*byte)(unsafe.Pointer(&linkedUserInfo)),
+ uint32(reflect.TypeOf(linkedUserInfo).Size()),
+ &usedMemory,
+ )
+ if err != nil {
+ return
+ }
+
+ defer syscall.CloseHandle(linkedUserInfo.LinkedToken)
+ linkedToken := syscall.Token(linkedUserInfo.LinkedToken)
+
+ // The buffer will also store the SID, therefore more than sizeof(TokenUser) bytes are required.
+ err = a.Config.authAPI.GetTokenInformation(
+ linkedToken,
+ uint32(syscall.TokenUser),
+ nil,
+ 0,
+ &usedMemory,
+ )
+ if err != syscall.ERROR_INSUFFICIENT_BUFFER {
+ return
+ }
+
+ buffer := make([]byte, int(usedMemory))
+ err = a.Config.authAPI.GetTokenInformation(
+ linkedToken,
+ uint32(syscall.TokenUser),
+ &buffer[0],
+ usedMemory,
+ &usedMemory,
+ )
+
+ tokenuser := (*TokenUser)(unsafe.Pointer(&buffer[0]))
+
+ if err != nil {
+ return
+ }
+
+ var stringsid string
+ stringsid, err = tokenuser.User.Sid.String()
+ if err != nil {
+ return
+
+ }
+
+ var lookedup *user.User
+ lookedup, err = user.LookupId(stringsid)
+ if err != nil {
+ return
+ }
+
+ u = &UserInfo{}
+ u.Username = lookedup.Username
+
+ if a.Config.EnumerateGroups {
+ if a.Config.ServerName == "" {
+ u.Groups, err = a.GetGroupsFromToken(linkedToken)
+ } else {
+ u.Groups, err = a.GetUserGroups(u.Username)
+ }
+ }
+
+ return
+}
+
+// GetGroups returns the groups assosiated with the specified security context
+func (a *Authenticator) GetGroups(context *CtxtHandle) (groups []string, err error) {
+ var token syscall.Token
+ token, err = a.GetAccessToken(context)
+ if err != nil {
+ return
+ }
+ return a.GetGroupsFromToken(token)
+}
+
+// GetGroupsFromToken returns the active groups of a Windows token
+func (a *Authenticator) GetGroupsFromToken(token syscall.Token) (groups []string, err error) {
var requiredMemory uint32
// 1. Get buffer size
ec := a.Config.authAPI.GetTokenInformation(
- syscall.Token(token.AccessToken),
+ syscall.Token(token),
syscall.TokenGroups,
nil, 0, &requiredMemory,
)
@@ -366,7 +461,7 @@ func (a *Authenticator) GetGroups(context *CtxtHandle) (groups []string, err err
tokenInformation := make([]byte, requiredMemory)
// 2. Get data
ec = a.Config.authAPI.GetTokenInformation(
- syscall.Token(token.AccessToken),
+ syscall.Token(token),
syscall.TokenGroups,
&tokenInformation[0], uint32(len(tokenInformation)), &requiredMemory,
)
@@ -480,6 +575,13 @@ func (a *Authenticator) GetUserInfo(context *CtxtHandle) (*UserInfo, error) {
}
}
+ if a.Config.ResolveLinked {
+ info.linked, err = a.GetLinkedUserInfo(context)
+ if err != nil {
+ return nil, err
+ }
+ }
+
return &info, nil
}
@@ -664,6 +766,9 @@ func (a *Authenticator) WithAuth(next http.Handler) http.Handler {
log.Print("Authenticated\n")
// Add the UserInfo value to the reqest's context
r = r.WithContext(context.WithValue(r.Context(), UserInfoKey, user))
+ if user.linked != nil {
+ r = r.WithContext(context.WithValue(r.Context(), LinkedTokenUserInfoKey, user.linked))
+ }
// and to the request header with key Config.AuthUserKey
if a.Config.AuthUserKey != "" {
r.Header.Set(a.Config.AuthUserKey, user.Username)
diff --git a/win32_windows.go b/win32_windows.go
index e96b92f..c05abbc 100644
--- a/win32_windows.go
+++ b/win32_windows.go
@@ -14,6 +14,14 @@ type TokenGroups struct {
Groups syscall.SIDAndAttributes // *SIDAndAttributes[]
}
+type TokenLinkedToken struct {
+ LinkedToken syscall.Handle
+}
+
+type TokenUser struct {
+ User syscall.SIDAndAttributes
+}
+
// secur32.dll
type SECURITY_STATUS syscall.Errno