Skip to content

Commit

Permalink
feat: improve support of users and teams
Browse files Browse the repository at this point in the history
  • Loading branch information
pandatix committed Feb 7, 2024
1 parent 67ceacf commit e8f47a9
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 46 deletions.
10 changes: 5 additions & 5 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func call(client *Client, req *http.Request, dst any, opts ...Option) error {
}

// Handle errors if any
if len(resp.Errors) != 0 {
if resp.Errors != nil {
return fmt.Errorf("CTFd responded with errors: %v", resp.Errors)
}
if !resp.Success {
Expand All @@ -151,10 +151,10 @@ func call(client *Client, req *http.Request, dst any, opts ...Option) error {
}

type Response struct {
Success bool `json:"success"`
Data any `json:"data,omitempty"`
Errors []string `json:"errors,omitempty"`
Message *string `json:"message,omitempty"`
Success bool `json:"success"`
Data any `json:"data,omitempty"`
Errors any `json:"errors,omitempty"` // can't type it to []string due to API model instabilities
Message *string `json:"message,omitempty"`
}

func get(client *Client, edp string, params any, dst any, opts ...Option) error {
Expand Down
34 changes: 17 additions & 17 deletions api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,23 +149,23 @@ type (
}

Team struct {
Bracket *string `json:"bracket"`
Members []int `json:"members,omitempty"`
ID int `json:"id"`
Created string `json:"created"`
Country *string `json:"country"`
Email *string `json:"email"`
Affiliation *string `json:"affiliation"`
CaptainID *int `json:"captain_id"`
Fields []Field `json:"fields"`
Banned bool `json:"banned"`
Website *string `json:"website"`
Hidden bool `json:"hidden"`
Secret *bool `json:"secret"`
Name string `json:"name"`
OauthID *string `json:"oauth_id"`
Place *string `json:"place,omitempty"`
Score *int `json:"score,omitempty"`
Bracket *string `json:"bracket"`
Members []int `json:"members,omitempty"`
ID int `json:"id"`
Created string `json:"created"`
Country *string `json:"country"`
Email *string `json:"email"`
Affiliation *string `json:"affiliation"`
CaptainID *int `json:"captain_id"`
Fields []string `json:"fields"`
Banned bool `json:"banned"`
Website *string `json:"website"`
Hidden bool `json:"hidden"`
Secret *bool `json:"secret"`
Name string `json:"name"`
OauthID *string `json:"oauth_id"`
Place *string `json:"place,omitempty"`
Score *int `json:"score,omitempty"`
}

User struct {
Expand Down
129 changes: 129 additions & 0 deletions api/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,132 @@ func Test_F_AdvancedSetup(t *testing.T) {
return
}
}

func Test_F_UsersAndTeams(t *testing.T) {
// Scenario:
//
// As an Ops, your job is to import all the registered users and teams
// before the event such that at the very beginning you are sure no one
// is lost.

assert := assert.New(t)

// 1a. Get nonce and session to mock a browser first
nonce, session, err := api.GetNonceAndSession(CTFD_URL)
if !assert.Nil(err, "got error: %s", err) {
return
}
client := api.NewClient(CTFD_URL, nonce, session, "")

t.Cleanup(func() {
_ = client.Reset(&api.ResetParams{
Accounts: ptr("y"),
Submissions: ptr("y"),
Challenges: ptr("y"),
Pages: ptr("y"),
Notifications: ptr("y"),
})
})

// 1b. Configure the CTF
err = client.Setup(&api.SetupParams{
CTFName: "CTFer",
CTFDescription: "Ephemeral CTFd running for API tests purposes.",
UserMode: "teams",
Name: "ctfer",
Email: "[email protected]",
Password: "password", // This is not real, don't bother trying x)
ChallengeVisibility: "admins",
AccountVisibility: "private",
ScoreVisibility: "hidden",
RegistrationVisibility: "mlc",
VerifyEmails: false,
TeamSize: ptr(4),
CTFLogo: nil,
CTFBanner: nil,
CTFSmallIcon: nil,
CTFTheme: "core",
ThemeColor: "",
Start: "",
End: "",
Nonce: nonce,
})
if !assert.Nil(err, "got error: %s", err) {
return
}

// 1c. Create an API Key to avoid session/nonce+cookies dance
token, err := client.PostTokens(&api.PostTokensParams{
Expiration: "2222-01-01",
Description: "Example API token.",
})
if !assert.Nil(err, "got error: %s", err) {
return
}
client.SetAPIKey(*token.Value)

// Define all users and teams
type User struct {
name, email, password string
}
type Team struct {
name, email, password string
users []User
}
var teams = []Team{
{
name: "MILF CTF Team",
email: "[email protected]",
password: "password",
users: []User{
{
name: "hashp4",
email: "[email protected]",
password: "password",
},
// ...
},
},
}

// 2. Create all the users and their teams
for _, team := range teams {
// 2a. Create team
tm, err := client.PostTeams(&api.PostTeamsParams{
Name: team.name,
Email: team.email,
Password: team.password,
Banned: false,
Hidden: false,
Fields: []api.Field{},
})
if !assert.Nil(err, "got error: %s", err) {
return
}

for _, user := range team.users {
// 2b. Create user
usr, err := client.PostUsers(&api.PostUsersParams{
Name: user.name,
Email: user.email,
Password: user.password,
Type: "user",
Verified: false,
Hidden: false,
Banned: false,
Fields: []api.Field{},
})
if !assert.Nil(err, "got error: %s", err) {
return
}

// 2c. Join user to team
_, err = client.PostTeamMembers(tm.ID, &api.PostTeamsMembers{
UserID: usr.ID,
})
if !assert.Nil(err, "got error: %s", err) {
return
}
}
}
}
39 changes: 24 additions & 15 deletions api/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ func (client *Client) GetTeams(params *GetTeamsParams, opts ...Option) ([]*Team,
}

type PostTeamsParams struct {
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
Banned bool `json:"banned"`
Hidden bool `json:"hidden"`
Fields []string `json:"fields"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Website *string `json:"website,omitempty"`
Affiliation *string `json:"affiliation,omitempty"`
Country *string `json:"country,omitempty"`
Banned bool `json:"banned"`
Hidden bool `json:"hidden"`
Fields []Field `json:"fields"`
}

func (client *Client) PostTeams(params *PostTeamsParams, opts ...Option) (*Team, error) {
Expand All @@ -48,11 +51,16 @@ func (client *Client) DeleteTeamsMe(opts ...Option) error {
}

type PatchTeamsParams struct {
CaptainID *int `json:"captain_id,omitempty"`
Banned *bool `json:"banned,omitempty"`
Fields []Field `json:"fields,omitempty"`
Hidden *bool `json:"hidden,omitempty"`
Name *string `json:"name,omitempty"`
CaptainID *int `json:"captain_id,omitempty"`
Name *string `json:"name,omitempty"`
Email *string `json:"email,omitempty"`
Password *string `json:"password,omitempty"`
Website *string `json:"website,omitempty"`
Affiliation *string `json:"affiliation,omitempty"`
Country *string `json:"country,omitempty"`
Banned *bool `json:"banned,omitempty"`
Hidden *bool `json:"hidden,omitempty"`
Fields []Field `json:"fields"`
}

func (client *Client) PatchTeamsMe(params *PatchTeamsParams, opts ...Option) (*Team, error) {
Expand Down Expand Up @@ -132,12 +140,13 @@ func (client *Client) DeleteTeamMembers(id int, params *DeleteTeamMembersParams,
return v, nil
}

func (client *Client) PostTeamMembers(id int, params *PostTeamsMembers, opts ...Option) (*Team, error) {
team := &Team{}
func (client *Client) PostTeamMembers(id int, params *PostTeamsMembers, opts ...Option) (int, error) {
// Use slice as a workaround due to API instabilities
var team []int
if err := post(client, fmt.Sprintf("/teams/%d/members", id), params, &team, opts...); err != nil {
return nil, err
return 0, err
}
return team, nil
return team[0], nil
}

func (client *Client) GetTeamAwards(id int, opts ...Option) ([]*Award, error) {
Expand Down
30 changes: 21 additions & 9 deletions api/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ func (client *Client) GetUsers(params *GetUsersParams, opts ...Option) ([]*User,
}

type PostUsersParams struct {
Name string `json:"name"`
Password string `json:"password"`
Email string `json:"email"`
Type string `json:"type"`
Banned bool `json:"banned"`
Hidden bool `json:"hidden"`
Verified bool `json:"verified"`
Fields []string `json:"fields"`
Name string `json:"name"`
Email string `json:"email"`
Language *string `json:"language,omitempty"`
Password string `json:"password"`
Website *string `json:"website,omitempty"`
Affiliation *string `json:"affiliation,omitempty"`
Country *string `json:"country,omitempty"`
Type string `json:"type"` // "user" or "admin"
Verified bool `json:"verified"`
Hidden bool `json:"hidden"`
Banned bool `json:"banned"`
Fields []Field `json:"fields"`
}

func (client *Client) PostUsers(params *PostUsersParams, opts ...Option) (*User, error) {
Expand All @@ -49,7 +53,15 @@ func (client *Client) GetUsersMe(opts ...Option) (*User, error) {
type PatchUsersParams struct {
Name string `json:"name"`
Email string `json:"email"`
Affiliation string `json:"affiliation"`
Language *string `json:"language,omitempty"`
Password *string `json:"password,omitempty"`
Website *string `json:"website,omitempty"`
Affiliation *string `json:"affiliation,omitempty"`
Country *string `json:"country,omitempty"`
Type string `json:"type"`
Verified bool `json:"verified"`
Hidden bool `json:"hidden"`
Banned bool `json:"banned"`
Fields []Field `json:"fields"`
}

Expand Down

0 comments on commit e8f47a9

Please sign in to comment.