workin oc-auth

This commit is contained in:
mr
2025-01-17 17:24:08 +01:00
parent fd65220b91
commit b84c2ef353
23 changed files with 551 additions and 104 deletions

View File

@@ -9,10 +9,10 @@ import (
type AuthConnector interface {
Status() tools.State
Login(username string, cookies ...*http.Cookie) (*Token, error)
Logout(token string, cookies ...*http.Cookie) (*Token, error)
Login(clientID string, username string, cookies ...*http.Cookie) (*Token, error)
Logout(clientID string, token string, cookies ...*http.Cookie) (*Token, error)
Introspect(token string, cookie ...*http.Cookie) (bool, error)
Refresh(token *Token) (*Token, error)
Refresh(client_id string, token *Token) (*Token, error)
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool
}

View File

@@ -23,7 +23,6 @@ import (
type HydraConnector struct {
State string `json:"state"`
Scopes string `json:"scope"`
ClientID string `json:"client_id"`
ResponseType string `json:"response_type"`
Caller *tools.HTTPCaller
@@ -85,7 +84,7 @@ func (a HydraConnector) challenge(username string, url string, challenge string,
return &token, s[1], cookies, nil
}
func (a HydraConnector) Refresh(token *Token) (*Token, error) {
func (a HydraConnector) Refresh(client_id string, token *Token) (*Token, error) {
access := strings.Split(token.AccessToken, ".")
if len(access) > 2 {
token.AccessToken = strings.Join(access[0:2], ".")
@@ -94,11 +93,11 @@ func (a HydraConnector) Refresh(token *Token) (*Token, error) {
if err != nil || !isValid {
return nil, err
}
_, err = a.Logout(token.AccessToken)
_, err = a.Logout(client_id, token.AccessToken)
if err != nil {
return nil, err
}
return a.Login(token.Username)
return a.Login(client_id, token.Username)
}
func (a HydraConnector) tryLog(username string, url string, subpath string, challenge string, cookies ...*http.Cookie) (*Redirect, string, []*http.Cookie, error) {
@@ -120,7 +119,7 @@ func (a HydraConnector) tryLog(username string, url string, subpath string, chal
return a.challenge(username, resp.Request.URL.String(), challenge, cookies...)
}
func (a HydraConnector) getClient() string {
func (a HydraConnector) getClient(clientID string) string {
resp, err := a.Caller.CallGet(a.getPath(true, false), "/clients")
if err != nil {
return ""
@@ -130,11 +129,17 @@ func (a HydraConnector) getClient() string {
if err != nil || len(clients) == 0 {
return ""
}
for _, c := range clients {
if c.(map[string]interface{})["client_name"].(string) == clientID {
return c.(map[string]interface{})["client_id"].(string)
}
}
return clients[0].(map[string]interface{})["client_id"].(string)
}
func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Token, err error) {
clientID := a.getClient()
func (a HydraConnector) Login(clientID string, username string, cookies ...*http.Cookie) (t *Token, err error) {
fmt.Println("login", clientID, username)
clientID = a.getClient(clientID)
redirect, _, cookies, err := a.tryLog(username, a.getPath(false, true),
"/auth?client_id="+clientID+"&response_type="+strings.ReplaceAll(a.ResponseType, " ", "%20")+"&scope="+strings.ReplaceAll(a.Scopes, " ", "%20")+"&state="+a.State,
"login", cookies...)
@@ -176,7 +181,7 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return nil, err
}
json.Unmarshal(b, &m)
pp := oclib.Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), oclib.LibDataEnum(oclib.PEER))
pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), false)
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
return nil, errors.New("peer not found")
}
@@ -184,7 +189,8 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
now = now.Add(time.Duration(token.ExpiresIn) * time.Second)
unix := now.Unix()
c := claims.GetClaims().AddClaimsToToken(username, pp.Data[0].(*peer.Peer))
c := claims.GetClaims().AddClaimsToToken(clientID, username, pp.Data[0].(*peer.Peer))
fmt.Println("claims", c.Session.AccessToken)
c.Session.AccessToken["exp"] = unix
b, _ = json.Marshal(c)
@@ -194,7 +200,8 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return token, nil
}
func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, error) {
func (a HydraConnector) Logout(clientID string, token string, cookies ...*http.Cookie) (*Token, error) {
clientID = a.getClient(clientID)
access := strings.Split(token, ".")
if len(access) > 2 {
token = strings.Join(access[0:2], ".")
@@ -202,7 +209,7 @@ func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, e
p := a.getPath(false, true) + "/revoke"
urls := url.Values{}
urls.Add("token", token)
urls.Add("client_id", a.getClient())
urls.Add("client_id", clientID)
urls.Add("client_secret", conf.GetConfig().ClientSecret)
_, err := a.Caller.CallForm(http.MethodPost, p, "", urls, "application/x-www-form-urlencoded", true)
if err != nil {

View File

@@ -31,8 +31,9 @@ var (
type conn interface {
Bind(bindDN, password string) error
SearchUser(user string, attrs ...string) ([]map[string]interface{}, error)
SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error)
SearchRoles(attrs ...string) ([]map[string][]string, error)
SearchUser(user string, attrs ...string) ([]map[string][]string, error)
SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error)
Close() error
}
@@ -78,7 +79,7 @@ type Client struct {
cache *freecache.Cache
}
func (cli *Client) Authenticate(ctx context.Context, username, password string) (bool, error) {
func (cli *Client) Authenticate(ctx context.Context, username string, password string) (bool, error) {
if username == "" || password == "" {
return false, nil
}
@@ -101,8 +102,8 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
if details == nil {
return false, nil
}
if err := cn.Bind(details["dn"].(string), password); err != nil {
a := details["dn"]
if err := cn.Bind(a[0], password); err != nil {
if err == errInvalidCredentials {
return false, nil
}
@@ -118,6 +119,21 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
return true, nil
}
func (cli *Client) GetRoles(ctx context.Context) (map[string]LDAPRoles, error) {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
cn, ok := <-cli.connect(ctx)
cancel()
if !ok {
return map[string]LDAPRoles{}, errConnectionTimeout
}
defer cn.Close()
// Find a user DN by his or her username.
return cli.findRoles(cn, "dn", "member", "uniqueMember")
}
// Claim is the FindOIDCClaims result struct
type LDAPClaim struct {
Code string // the root claim name
@@ -125,6 +141,10 @@ type LDAPClaim struct {
Value interface{} // the value
}
type LDAPRoles struct {
Members map[string][]string
}
// FindOIDCClaims finds all OIDC claims for a user.
func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPClaim, error) {
if username == "" {
@@ -193,11 +213,12 @@ func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPC
roles := make(map[string]interface{})
for _, entry := range entries {
roleDN, ok := entry["dn"].(string)
if !ok || roleDN == "" {
roleDNs, ok := entry["dn"]
if !ok || len(roleDNs) == 0 {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", "dn", "entry", entry)
continue
}
roleDN := roleDNs[0]
if entry[cli.RoleAttr] == nil {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", cli.RoleAttr, "roleDN", roleDN)
continue
@@ -278,8 +299,79 @@ func (cli *Client) connect(ctx context.Context) <-chan conn {
return ch
}
func (cli *Client) findRoles(cn conn, attrs ...string) (map[string]LDAPRoles, error) {
if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
return map[string]LDAPRoles{}, errors.New(err.Error() + " : failed to login to a LDAP woth a service account")
}
}
entries, err := cn.SearchRoles(attrs...)
fmt.Println("entries", entries)
if err != nil {
return map[string]LDAPRoles{}, err
}
claims := map[string]LDAPRoles{}
for _, entry := range entries {
roleDNs, ok := entry["dn"]
if !ok || len(roleDNs) == 0 {
continue
}
roleDN := roleDNs[0]
// Ensure that a role's DN is inside of the role's base DN.
// It's sufficient to compare the DN's suffix with the base DN.
n, k := len(roleDN), len(cli.RoleBaseDN)
if n < k || !strings.EqualFold(roleDN[n-k:], cli.RoleBaseDN) {
panic("You should never see that")
}
// The DN without the role's base DN must contain a CN and OU
// where the CN is for uniqueness only, and the OU is an application id.
path := strings.Split(roleDN[:n-k-1], ",")
if len(path) != 2 {
continue
}
appID := path[1][len("OU="):]
if _, ok := claims[appID]; !ok {
claims[appID] = LDAPRoles{
Members: map[string][]string{},
}
}
role := path[0][len("cn="):]
if claims[appID].Members[role] == nil {
claims[appID].Members[role] = []string{}
}
fmt.Println("entry", entry)
memberDNs, ok := entry["member"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
memberDNs, ok = entry["uniqueMember"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
}
return claims, nil
}
// findBasicUserDetails finds user's LDAP attributes that were specified. It returns nil if no such user.
func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string]interface{}, error) {
func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string][]string, error) {
if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
@@ -298,7 +390,7 @@ func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string
var (
entry = entries[0]
details = make(map[string]interface{})
details = make(map[string][]string)
)
for _, attr := range attrs {
if v, ok := entry[attr]; ok {
@@ -349,35 +441,40 @@ func (c *ldapConn) Bind(bindDN, password string) error {
return err
}
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) {
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf(
"(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))"+
"(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))", user)
return c.searchEntries(c.BaseDN, query, attrs)
}
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) {
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf("(|"+
"(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))"+
"(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))(member=%[1]s))"+
"(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s))"+
")", user)
return c.searchEntries(c.RoleBaseDN, query, attrs)
}
func (c *ldapConn) SearchRoles(attrs ...string) ([]map[string][]string, error) {
query := "(|(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))))"
return c.searchEntries(c.RoleBaseDN, query, attrs)
}
// searchEntries executes a LDAP query, and returns a result as entries where each entry is mapping of LDAP attributes.
func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string]interface{}, error) {
func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string][]string, error) {
req := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, query, attrs, nil)
res, err := c.Search(req)
if err != nil {
return nil, err
}
var entries []map[string]interface{}
var entries []map[string][]string
for _, v := range res.Entries {
entry := map[string]interface{}{"dn": v.DN}
entry := map[string][]string{"dn": []string{v.DN}}
for _, attr := range v.Attributes {
// We need the first value only for the named attribute.
entry[attr.Name] = attr.Values[0]
entry[attr.Name] = attr.Values
}
entries = append(entries, entry)
}