Skip to content

Commit

Permalink
feat: add support for nested OAuth2 client metadata
Browse files Browse the repository at this point in the history
Co-authored-by: Aneliya Ivanova <[email protected]>
  • Loading branch information
YevheniiSemenko and Aneliya Ivanova authored Sep 26, 2024
1 parent 073b49c commit 946891b
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
version: v1.55.2
- name: Run services
run: docker-compose up -d
run: docker compose up -d
- name: Test
env:
HYDRA_ADMIN_URL: http://localhost:4445
Expand Down
10 changes: 9 additions & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
version: 2

before:
hooks:
- go mod tidy

builds:
- env:
- CGO_ENABLED=0
Expand All @@ -21,12 +24,15 @@ builds:
- goos: darwin
goarch: "386"
binary: "{{ .ProjectName }}_v{{ .Version }}"

archives:
- format: zip
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"

checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_SHA256SUMS"
algorithm: sha256

signs:
- artifacts: checksum
args:
Expand All @@ -37,7 +43,9 @@ signs:
- "${signature}"
- "--detach-sign"
- "${artifact}"

release:
draft: true

changelog:
skip: true
disable: true
14 changes: 11 additions & 3 deletions docs/resources/oauth2_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ resource "hydra_oauth2_client" "example" {
metadata = {
"first_party" = true
}
redirect_uris = ["http://localhost:8080/callback"]
response_types = ["code"]
token_endpoint_auth_method = "none"
Expand Down Expand Up @@ -75,6 +75,16 @@ The JWK x5c parameter MAY be used to provide X.509 representations of keys provi
- `jwt_bearer_grant_access_token_lifespan` (String) Specify a time duration in milliseconds, seconds, minutes, hours.
- `logo_uri` (String) LogoURI is an URL string that references a logo for the client.
- `metadata` (Map of String)
- `metadata_json` (String) A JSON-encoded string representing complex, nested metadata. This allows for more advanced configurations compared to the flat key-value pairs in the `metadata` field. Use `jsonencode` to provide this value in Terraform configurations. This field conflicts with `metadata`.
#### Example
```terraform
metadata_json = jsonencode({
"nested_key" = {
"inner_key" = "value"
},
"first_party" = true
})
```
- `owner` (String) Owner is a string identifying the owner of the OAuth 2.0 Client.
- `policy_uri` (String) PolicyURI is a URL string that points to a human-readable privacy policy document that describes how the deployment organization collects, uses, retains, and discloses personal data.
- `post_logout_redirect_uris` (List of String)
Expand Down Expand Up @@ -125,5 +135,3 @@ Optional:
- `x` (String, Sensitive)
- `x5c` (List of String)
- `y` (String, Sensitive)


33 changes: 33 additions & 0 deletions internal/provider/helper.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package provider

import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func ptr[T any](v T) *T {
Expand Down Expand Up @@ -74,3 +77,33 @@ func validateDuration(val interface{}, key string) (ws []string, errors []error)
}
return
}

// checkResourceAttrJSON compares the JSON structure by unmarshalling both the actual and expected values into Go maps and comparing those, rather than comparing the raw JSON strings.
func checkResourceAttrJSON(resourceName, attributeName, expectedJSON string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

actualValue := rs.Primary.Attributes[attributeName]
var actualMap, expectedMap map[string]interface{}

if err := json.Unmarshal([]byte(actualValue), &actualMap); err != nil {
return fmt.Errorf("failed to unmarshal actual JSON: %s", err)
}
if err := json.Unmarshal([]byte(expectedJSON), &expectedMap); err != nil {
return fmt.Errorf("failed to unmarshal expected JSON: %s", err)
}

if !equalJSONMaps(actualMap, expectedMap) {
return fmt.Errorf("JSON mismatch for attribute %s: expected %v, got %v", attributeName, expectedMap, actualMap)
}

return nil
}
}

func equalJSONMaps(a, b map[string]interface{}) bool {
return reflect.DeepEqual(a, b)
}
47 changes: 45 additions & 2 deletions internal/provider/resource_oauth2_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provider

import (
"context"
"encoding/json"
"errors"
"net/http"
"regexp"
Expand Down Expand Up @@ -140,12 +141,19 @@ The JWK x5c parameter MAY be used to provide X.509 representations of keys provi
Optional: true,
Description: "LogoURI is an URL string that references a logo for the client.",
},
"metadata_json": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringIsJSON,
ConflictsWith: []string{"metadata"},
},
"metadata": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
ConflictsWith: []string{"metadata_json"},
},
"owner": {
Type: schema.TypeString,
Expand Down Expand Up @@ -423,7 +431,34 @@ func dataFromClient(data *schema.ResourceData, oAuthClient *hydra.OAuth2Client)
dataFromJWKS(data, jwks, "jwk")
data.Set("jwks_uri", oAuthClient.GetJwksUri())
data.Set("logo_uri", oAuthClient.GetLogoUri())
data.Set("metadata", oAuthClient.Metadata)
if metadata, ok := oAuthClient.Metadata.(map[string]interface{}); ok {
// Check if any nested maps or non-string values exist in metadata
useMetadataJSON := false
for _, v := range metadata {
switch v.(type) {
case string:
continue
default:
useMetadataJSON = true
}
}
// If metadata contains nested structures or non-string values, use metadata_json
if useMetadataJSON {
metadataJSON, err := json.Marshal(metadata)
if err != nil {
return err
}
data.Set("metadata_json", string(metadataJSON))
data.Set("metadata", nil)
} else {
// If no nested structures or non-string values, use metadata
data.Set("metadata", metadata)
data.Set("metadata_json", nil)
}
} else {
data.Set("metadata", nil)
data.Set("metadata_json", nil)
}
data.Set("owner", oAuthClient.Owner)
data.Set("policy_uri", oAuthClient.GetPolicyUri())
data.Set("post_logout_redirect_uris", oAuthClient.PostLogoutRedirectUris)
Expand Down Expand Up @@ -485,7 +520,15 @@ func dataToClient(data *schema.ResourceData) *hydra.OAuth2Client {
}
client.SetJwksUri(data.Get("jwks_uri").(string))
client.SetLogoUri(data.Get("logo_uri").(string))
client.Metadata = data.Get("metadata")
if metadataJSON, ok := data.GetOk("metadata_json"); ok {
var metadata map[string]interface{}
err := json.Unmarshal([]byte(metadataJSON.(string)), &metadata)
if err == nil {
client.Metadata = metadata
}
} else if metadata, ok := data.GetOk("metadata"); ok {
client.Metadata = metadata.(map[string]interface{})
}
if o, ok := data.GetOk("owner"); ok {
client.Owner = ptr(o.(string))
}
Expand Down
30 changes: 30 additions & 0 deletions internal/provider/resource_oauth2_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ func TestAccResourceOAuth2Client(t *testing.T) {
resource.TestCheckResourceAttr("hydra_oauth2_client.secret", "token_endpoint_auth_method", "client_secret_post"),
),
},
{
Config: testAccResourceOAuth2ClientWithMetadataJSON,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "client_name", "client_with_metadata_json"),
checkResourceAttrJSON("hydra_oauth2_client.client_with_metadata_json", "metadata_json", `{"nested": {"key": "value"}, "first_party": true}`),
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "redirect_uris.#", "1"),
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "redirect_uris.0", "http://localhost:8080/callback"),
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "response_types.#", "1"),
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "response_types.0", "code"),
resource.TestCheckResourceAttr("hydra_oauth2_client.client_with_metadata_json", "token_endpoint_auth_method", "none"),
),
},
},
})
}
Expand Down Expand Up @@ -67,4 +79,22 @@ resource "hydra_oauth2_client" "secret" {
response_types = ["code"]
token_endpoint_auth_method = "client_secret_post"
}`

testAccResourceOAuth2ClientWithMetadataJSON = `
provider "hydra" {
endpoint = "http://localhost:4445"
}
resource "hydra_oauth2_client" "client_with_metadata_json" {
client_name = "client_with_metadata_json"
metadata_json = jsonencode({
"nested" = {
"key" = "value"
},
"first_party" = true
})
redirect_uris = ["http://localhost:8080/callback"]
response_types = ["code"]
token_endpoint_auth_method = "none"
}`
)

0 comments on commit 946891b

Please sign in to comment.