package keycloak import ( "context" "fmt" "net/http" "strings" ) // userRepresentation is the slice of the Admin API's UserRepresentation we // need. Attributes arrive as map[name][]values; the KC admin console writes // multivalued attributes either as separate list entries or as one entry // joined with "##", so both shapes must be accepted. type userRepresentation struct { ID string `json:"id"` Username string `json:"username"` Enabled bool `json:"enabled"` Attributes map[string][]string `json:"attributes"` } // Memberships implements Adapter against GET /admin/realms/{realm}/users/{id}. func (a *HTTPAdapter) Memberships(ctx context.Context, userID string) ([]Claims, error) { var u userRepresentation resp, err := a.adminCall(ctx, http.MethodGet, "/users/"+userID, nil, &u) if err != nil { return nil, err } if resp.StatusCode == http.StatusNotFound { _ = resp.Body.Close() return nil, ErrUserNotFound } if resp.StatusCode/100 != 2 { _ = resp.Body.Close() return nil, fmt.Errorf("keycloak get user: %d", resp.StatusCode) } return claimsFromAttributes(u.Attributes), nil } // claimsFromAttributes builds the membership list from a user's attribute // projection. A user with no tenant_id and no tenant_slug attribute simply // has no memberships — that is a valid state, not an error. func claimsFromAttributes(attrs map[string][]string) []Claims { c := Claims{ TenantID: attrValue(attrs, "tenant_id"), TenantSlug: attrValue(attrs, "tenant_slug"), OrgRoles: attrValues(attrs, "org_roles"), Products: attrValues(attrs, "products"), Plan: attrValue(attrs, "plan"), TenantStatus: attrValue(attrs, "tenant_status"), } if c.TenantID == "" && c.TenantSlug == "" { return []Claims{} } return []Claims{c} } func attrValue(attrs map[string][]string, key string) string { if vs := attrValues(attrs, key); len(vs) > 0 { return vs[0] } return "" } func attrValues(attrs map[string][]string, key string) []string { out := []string{} for _, entry := range attrs[key] { for _, v := range strings.Split(entry, "##") { if v = strings.TrimSpace(v); v != "" { out = append(out, v) } } } return out }