Skip to content

Commit

Permalink
feat!(api) : update to CTFd 3.7.1
Browse files Browse the repository at this point in the history
Breaking changes are independent of our work but is rather due to API inconsistencies in the CTFd update.
  • Loading branch information
pandatix committed Jun 1, 2024
1 parent 91250d2 commit fa12bbc
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

Golang client for interacting with [CTFd](https://ctfd.io/).

Last version tested on: [3.7.0](https://github.com/CTFd/CTFd/releases/tag/3.7.0).
Last version tested on: [3.7.1](https://github.com/CTFd/CTFd/releases/tag/3.7.1).
52 changes: 52 additions & 0 deletions api/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package api

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

type ExportRawParams struct {
// Type could be "csv" (which returns the table dump in csv) or
// anything (returns a zip).
Type string `json:"type"`

// Args contains all the arguments to export specific content.
Args ExportRawArgsParams `json:"args"`
}

type ExportRawArgsParams struct {
// Table, if specified, exports the given table in the specified type.
Table *string `json:"table,omitempty"`
}

func (client *Client) ExportRaw(params *ExportRawParams, opts ...Option) ([]byte, error) {
if params == nil {
params = &ExportRawParams{}
}
i, err := json.Marshal(params)
if err != nil {
return nil, err
}

// Build request and execute it manunally
req, _ := http.NewRequest(http.MethodPost, "/api/v1/exports/raw", bytes.NewBuffer(i))
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("CTFd responded with unexpected status code: got %d", res.StatusCode)
}

b, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
return b, nil
}
10 changes: 7 additions & 3 deletions api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ type (
TeamID int `json:"team_id"` // XXX may be duplicated with team.id ?
IP string `json:"ip"`
ChallengeID int `json:"challenge_id"`
UserID int `json:"user_id"` // XXX may be duplicated with user.id ?
AccountID int `json:"account_id"` // XXX introducted in 3.7.1, clearly a duplication of user_id...
UserID int `json:"user_id"` // XXX may be duplicated with user.id ?
Value int `json:"value"`
Team struct {
ID int `json:"id"`
Name string `json:"name"`
Expand All @@ -141,6 +143,7 @@ type (
}

Scoreboard struct {
ID int `json:"id"`
Pos int `json:"pos"`
AccountId int `json:"account_id"`
AccountURL string `json:"account_url"`
Expand All @@ -154,8 +157,9 @@ type (
Name string `json:"name"`
Score int `json:"score"`
} `json:"members"`
BracketID *string `json:"bracket_id"`
BracketName *string `json:"bracket_name"`
Solves []*Submission `json:"solves"`
BracketID *string `json:"bracket_id"`
BracketName *string `json:"bracket_name"`
}

Team struct {
Expand Down
17 changes: 14 additions & 3 deletions api/scoreboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,20 @@ func (client *Client) GetScoreboard(opts ...Option) ([]*Scoreboard, error) {
return sb, nil
}

func (client *Client) GetScoreboardTop(count int, opts ...Option) ([]*Scoreboard, error) {
sb := []*Scoreboard{}
if err := get(client, fmt.Sprintf("/scoreboard/top/%d", count), nil, &sb, opts...); err != nil {
// GetScoreboardTopParams holds the parameters for the scoreboard top count endpoint.
type GetScoreboardTopParams struct {
// Count is the top number of players to get the info.
Count int `schema:"-"`

// BracketID is an optional parameter to filter on a specific bracket.
BracketID *int `schema:"bracket_id,omitempty"`
}

// GetScoreboardTop returns the scoreboard top for the given count as a map
// of the rank by the entry.
func (client *Client) GetScoreboardTop(params *GetScoreboardTopParams, opts ...Option) (map[string]*Scoreboard, error) {
sb := map[string]*Scoreboard{}
if err := get(client, fmt.Sprintf("/scoreboard/top/%d", params.Count), params, &sb, opts...); err != nil {
return nil, err
}
return sb, nil
Expand Down
15 changes: 14 additions & 1 deletion examples/setup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func main() {
// Create API Key
fmt.Println("[+] Creating API Token")
token, err := client.PostTokens(&ctfd.PostTokensParams{
Expiration: "2024-05-14",
Expiration: "2222-02-02",
Description: "Example API token.",
})
if err != nil {
Expand Down Expand Up @@ -143,6 +143,19 @@ func main() {

fmt.Printf("ch: %+v\n", ch)
fmt.Printf("ch.Requirements: %+v\n", ch.Requirements)

// Export challenges
fmt.Println("[+] Exporting table \"challenges\"")
b, err := client.ExportRaw(&ctfd.ExportRawParams{
Type: "csv",
Args: ctfd.ExportRawArgsParams{
Table: ptr("challenges"),
},
})
if err != nil {
log.Fatalf(" Failed: %s", err)
}
fmt.Printf("b: %s\n", b)
}

func ptr[T any](t T) *T {
Expand Down

0 comments on commit fa12bbc

Please sign in to comment.