Skip to content

Commit

Permalink
Merge pull request #227 from eurofurence/issue-225-badge-flags-tooling
Browse files Browse the repository at this point in the history
Issue 225 badge flags tooling
  • Loading branch information
Jumpy-Squirrel authored Aug 18, 2024
2 parents 3a57415 + e869079 commit dabd989
Show file tree
Hide file tree
Showing 5 changed files with 658 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ test/contract/*/logs/**
attendee-service
main

api-generator
api-generator
**/config.yaml
67 changes: 67 additions & 0 deletions cmd/staffbadges/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"errors"
"fmt"
"gopkg.in/yaml.v2"
"log"
"os"
"strings"
)

type Config struct {
Token string `yaml:"idp_token"` // can take from AUTH cookie in regsys for a user that is staff + director
IDPUrl string `yaml:"idp_url"` // base url with no trailing /
Jwt string `yaml:"jwt"` // can take from admin auth in regsys
Auth string `yaml:"auth"` // can take from admin auth in regsys
RegsysUrl string `yaml:"regsys_url"` // base url including context including /attsrv, no trailing /
StaffGroupID string `yaml:"staff_group_id"` // look up in IDP in url
DirectorsGroupID string `yaml:"directors_group_id"` // look up in IDP in url
}

func (c *Config) validate() error {
if c.Token == "" {
return errors.New("identity provider token empty")
}
if !strings.HasPrefix(c.IDPUrl, "https://") || strings.HasSuffix(c.IDPUrl, "/") {
return errors.New("invalid identity provider url")
}
if jwtParts := strings.Split(c.Jwt, "."); len(jwtParts) != 3 {
return errors.New("invalid jwt cookie, must contain full jwt with all 3 parts")
}
if c.Auth == "" {
return errors.New("invalid auth cookie")
}
if !strings.HasPrefix(c.RegsysUrl, "http") || strings.HasSuffix(c.RegsysUrl, "/") {
return errors.New("invalid regsys base url")
}
if c.StaffGroupID == "" {
return errors.New("staff group id missing")
}
if c.DirectorsGroupID == "" {
return errors.New("directors group id missing")
}
return nil
}

func loadValidatedConfig() (Config, error) {
log.Println("reading configuration")

result := Config{}

yamlFile, err := os.ReadFile("cmd/staffbadges/config.yaml")
if err != nil {
return result, fmt.Errorf("failed to load config.yaml: %s", err.Error())
}

if err := yaml.UnmarshalStrict(yamlFile, &result); err != nil {
return result, fmt.Errorf("failed to parse config.yaml: %s", err.Error())
}

if err := result.validate(); err != nil {
return result, fmt.Errorf("failed to validate configuration: %s", err.Error())
}

log.Println("successfully read and validated configuration")
return result, nil
}
124 changes: 124 additions & 0 deletions cmd/staffbadges/idpquery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)

type IDPLookupResult struct {
StaffIDs []string
DirectorIDs []string
}

func lookupUserIDs(config Config) (IDPLookupResult, error) {
result := IDPLookupResult{}

staffIDs, err := lookupUserIDsForGroup(config.IDPUrl, config.StaffGroupID, config.Token)
if err != nil {
return result, err
}

directorIDs, err := lookupUserIDsForGroup(config.IDPUrl, config.DirectorsGroupID, config.Token)
if err != nil {
return result, err
}

// remove staffIDs that are also directors
result.DirectorIDs = make([]string, 0)
for k, _ := range directorIDs {
if _, ok := staffIDs[k]; ok {
log.Printf("removing staff %s who is also director\n", k)
delete(staffIDs, k)
}
result.DirectorIDs = append(result.DirectorIDs, k)
}

result.StaffIDs = make([]string, 0)
for k, _ := range staffIDs {
result.StaffIDs = append(result.StaffIDs, k)
}

log.Printf("we now have %d directors and %d staff", len(result.DirectorIDs), len(result.StaffIDs))
return result, nil
}

// --- internals ---

type groupUsersResponse struct {
Data []struct {
GroupId string `json:"group_id"`
UserId string `json:"user_id"`
Level string `json:"level"`
} `json:"data"`
Links struct {
First string `json:"first"`
Last interface{} `json:"last"`
Prev interface{} `json:"prev"`
Next string `json:"next"` // if null, no next page
} `json:"links"`
Meta struct {
CurrentPage int `json:"current_page"`
From int `json:"from"`
Path string `json:"path"`
PerPage int `json:"per_page"`
To int `json:"to"`
} `json:"meta"`
}

func requestGroupUsers(url string, token string) (groupUsersResponse, error) {
result := groupUsersResponse{}

request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return result, fmt.Errorf("error creating request: %s", err.Error())
}
request.Header.Add("Authorization", "Bearer "+token)

response, err := http.DefaultClient.Do(request)
if err != nil {
return result, fmt.Errorf("error making request: %s", err.Error())
}

defer response.Body.Close()
body, err := io.ReadAll(response.Body)
if err != nil {
return result, fmt.Errorf("error reading body: %s", err.Error())
}

err = json.Unmarshal(body, &result)
if err != nil {
return result, fmt.Errorf("error parsing body: %s", err.Error())
}

return result, nil
}

func lookupUserIDsForGroup(baseUrl string, grpId string, token string) (map[string]struct{}, error) {
log.Println("querying idp for group " + grpId)

result := make(map[string]struct{})

url := fmt.Sprintf("%s/api/v1/groups/%s/users?page=1", baseUrl, grpId)
response, err := requestGroupUsers(url, token)
if err != nil {
return result, err
}
for _, entry := range response.Data {
result[entry.UserId] = struct{}{}
}
for response.Links.Next != "" {
response, err = requestGroupUsers(response.Links.Next, token)
if err != nil {
return result, err
}
for _, entry := range response.Data {
result[entry.UserId] = struct{}{}
}
}

log.Printf("successfully read %d member ids in group %s\n", len(result), grpId)
return result, nil
}
Loading

0 comments on commit dabd989

Please sign in to comment.