Skip to content

Commit

Permalink
fix(challenge): make function non mandatory
Browse files Browse the repository at this point in the history
Closes #132
  • Loading branch information
pandatix committed Dec 13, 2024
1 parent 99ec1b6 commit aa02466
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 26 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/ctfer-io/terraform-provider-ctfd
go 1.23.2

require (
github.com/ctfer-io/go-ctfd v0.10.0
github.com/ctfer-io/go-ctfd v0.10.1
github.com/hashicorp/terraform-plugin-docs v0.20.1
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-go v0.25.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/ctfer-io/go-ctfd v0.10.0 h1:+lBLqde9XNdLB+TzXvJg9I9qkLRA8rYmf9bpjQ2CAUg=
github.com/ctfer-io/go-ctfd v0.10.0/go.mod h1:jNeeXui6iW1jsAfa4D5v6MaK0UF26CtjTRtCqS/PWH4=
github.com/ctfer-io/go-ctfd v0.10.1 h1:Bxr6/qo/h1c0FYFNZv/ej839Hzo5x1VMs0v+Io8ovs8=
github.com/ctfer-io/go-ctfd v0.10.1/go.mod h1:ebgSW8LdP/qtRCpglK4djBp+g6kU5YM98XBZKowiCeY=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -189,8 +189,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
Expand Down
65 changes: 44 additions & 21 deletions provider/challenge_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
Expand Down Expand Up @@ -102,34 +100,23 @@ func (r *challengeResource) Schema(ctx context.Context, req resource.SchemaReque
MarkdownDescription: "Decay function to define how the challenge value evolve through solves, either linear or logarithmic.",
Optional: true,
Computed: true,
Default: defaults.String(stringdefault.StaticString("linear")),
Validators: []validator.String{
validators.NewStringEnumValidator([]basetypes.StringValue{
FunctionLinear,
FunctionLogarithmic,
}),
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"value": schema.Int64Attribute{
MarkdownDescription: "The value (points) of the challenge once solved. Internally, the provider will handle what target is legitimate depending on the `.type` value, i.e. either `value` for \"standard\" or `initial` for \"dynamic\".",
Required: true,
},
// XXX decay and minimum are only required if .type == "dynamic"
"decay": schema.Int64Attribute{
MarkdownDescription: "The decay defines from each number of solves does the decay function triggers until reaching minimum. This function is defined by CTFd and could be configured through `.function`.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"minimum": schema.Int64Attribute{
MarkdownDescription: "The minimum points for a dynamic-score challenge to reach with the decay function. Once there, no solve could have more value.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"state": schema.StringAttribute{
MarkdownDescription: "State of the challenge, either hidden or visible.",
Expand Down Expand Up @@ -228,6 +215,26 @@ func (r *challengeResource) Create(ctx context.Context, req resource.CreateReque
return
}

// Configuration checks
if data.Type.Equal(types.StringValue("dynamic")) {
if data.Decay.IsNull() {
resp.Diagnostics.AddError("Configuration error", "decay must be set for dynamic challenges")
}
if data.Minimum.IsNull() {
resp.Diagnostics.AddError("Configuration error", "minimum must be set for dynamic challenges")
}
if data.Function.IsNull() || data.Function.IsUnknown() {
data.Function = FunctionLogarithmic
}
} else {
if data.Function.IsNull() || data.Function.IsUnknown() {
data.Function = types.StringNull()
}
}
if resp.Diagnostics.HasError() {
return
}

// Create Challenge
reqs := (*api.Requirements)(nil)
if data.Requirements != nil {
Expand All @@ -247,11 +254,11 @@ func (r *challengeResource) Create(ctx context.Context, req resource.CreateReque
Description: data.Description.ValueString(),
ConnectionInfo: data.ConnectionInfo.ValueStringPointer(),
MaxAttempts: utils.ToInt(data.MaxAttempts),
Function: data.Function.ValueString(),
Function: data.Function.ValueStringPointer(),
Value: int(data.Value.ValueInt64()),
Initial: utils.ToInt(data.Value),
Decay: utils.ToInt(data.Decay),
Minimum: utils.ToInt(data.Minimum),
Initial: utils.ToIntOnDynamic(data.Value, data.Type),
Decay: utils.ToIntOnDynamic(data.Decay, data.Type),
Minimum: utils.ToIntOnDynamic(data.Minimum, data.Type),
State: data.State.ValueString(),
Type: data.Type.ValueString(),
NextID: utils.ToInt(data.Next),
Expand Down Expand Up @@ -343,6 +350,22 @@ func (r *challengeResource) Update(ctx context.Context, req resource.UpdateReque
var dataState challengeResourceModel
req.State.Get(ctx, &dataState)

// Configuration checks
if data.Type.Equal(types.StringValue("dynamic")) {
if data.Decay.IsNull() {
resp.Diagnostics.AddError("Configuration error", "decay must be set for dynamic challenges")
}
if data.Minimum.IsNull() {
resp.Diagnostics.AddError("Configuration error", "minimum must be set for dynamic challenges")
}
if data.Function.IsNull() {
data.Function = FunctionLogarithmic
}
}
if resp.Diagnostics.HasError() {
return
}

// Patch direct attributes
reqs := (*api.Requirements)(nil)
if data.Requirements != nil {
Expand All @@ -362,7 +385,7 @@ func (r *challengeResource) Update(ctx context.Context, req resource.UpdateReque
Description: data.Description.ValueString(),
ConnectionInfo: data.ConnectionInfo.ValueStringPointer(),
MaxAttempts: utils.ToInt(data.MaxAttempts),
Function: data.Function.ValueString(),
Function: data.Function.ValueStringPointer(),
Value: utils.ToInt(data.Value),
Initial: utils.ToInt(data.Value),
Decay: utils.ToInt(data.Decay),
Expand Down Expand Up @@ -499,7 +522,7 @@ func (chall *challengeResourceModel) Read(ctx context.Context, client *api.Clien
chall.Description = types.StringValue(res.Description)
chall.ConnectionInfo = utils.ToTFString(res.ConnectionInfo)
chall.MaxAttempts = utils.ToTFInt64(res.MaxAttempts)
chall.Function = types.StringValue(res.Function)
chall.Function = utils.ToTFString(res.Function)
chall.Decay = utils.ToTFInt64(res.Decay)
chall.Minimum = utils.ToTFInt64(res.Minimum)
chall.State = types.StringValue(res.State)
Expand Down
2 changes: 2 additions & 0 deletions provider/challenge_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ resource "ctfd_challenge" "icmp" {
- NicolasFgrx
EOT
value = 500
type = "standard"
requirements = {
behavior = "anonymized"
Expand All @@ -96,6 +97,7 @@ resource "ctfd_challenge" "icmp" {
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("ctfd_challenge.icmp", "requirements.prerequisites.#", "1"),
resource.TestCheckNoResourceAttr("ctfd_challenge.icmp", "function"),
),
},
// Delete testing automatically occurs in TestCase
Expand Down
9 changes: 9 additions & 0 deletions provider/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ func ToInt(itf types.Int64) *int {
return &i
}

// ToIntOnDynamic returns the value of itf as an integer pointer iif
// the challType is dynamic.
func ToIntOnDynamic(itf types.Int64, challType types.String) *int {
if challType == types.StringValue("dynamic") {
return ToInt(itf)
}
return nil
}

func Ptr[T any](t T) *T {
return &t
}
Expand Down

0 comments on commit aa02466

Please sign in to comment.