diff --git a/cmd/dcr.go b/cmd/dcr.go index c059781..d59f1f5 100644 --- a/cmd/dcr.go +++ b/cmd/dcr.go @@ -35,10 +35,10 @@ func registerCmd(imsConfig *ims.Config) *cobra.Command { Use: "register", Short: "Register a client.", Long: `Register a new OAuth client using Dynamic Client Registration.`, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, asrgs []string) error { cmd.SilenceUsage = true - resp, err := imsConfig.Register() + resp, err := imsConfig.DCRRegister() if err != nil { return fmt.Errorf("error during client registration: %w", err) } @@ -50,6 +50,7 @@ func registerCmd(imsConfig *ims.Config) *cobra.Command { cmd.Flags().StringVarP(&imsConfig.ClientName, "clientName", "n", "", "Client application name.") cmd.Flags().StringSliceVarP(&imsConfig.RedirectURIs, "redirectURIs", "r", []string{}, "Redirect URIs (comma-separated or multiple flags).") + cmd.Flags().StringSliceVarP(&imsConfig.Scopes, "scopes", "s", []string{}, "Requested scopes (comma-separated or multiple flags).") return cmd } diff --git a/demo.sh b/demo.sh new file mode 100755 index 0000000..b3a0af4 --- /dev/null +++ b/demo.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +run() { + echo -e "\e[33m\$ $*\e[0m" + read -r + "$@" + echo "" +} + +clear +echo "imscli Demo" +echo "" +read -r + +# 1. DCR +run ./imscli dcr register \ + --url https://ims-na1-stg1.adobelogin.com \ + --clientName "My App" \ + --redirectURIs "http://localhost:8888" \ + --scopes "openid AdobeID" + +read -r -p "client_id: " CLIENT_ID +read -r -p "client_secret: " CLIENT_SECRET + +# 2. Authz +echo -e "\e[33m\$ ./imscli authz user --url https://ims-na1-stg1.adobelogin.com --clientID $CLIENT_ID --clientSecret $CLIENT_SECRET --scopes \"openid AdobeID\" --organization \"96D656605F9846070A494236@AdobeOrg\"\e[0m" +read -r +ACCESS_TOKEN=$(./imscli authz user \ + --url https://ims-na1-stg1.adobelogin.com \ + --clientID "$CLIENT_ID" \ + --clientSecret "$CLIENT_SECRET" \ + --scopes "openid AdobeID" \ + --organization "96D656605F9846070A494236@AdobeOrg") +echo "$ACCESS_TOKEN" +echo "" + +# 3. OBO +run ./imscli obo \ + --url https://ims-na1-stg1.adobelogin.com \ + --clientID "imscli" \ + --clientSecret "s8e-QZ9CnNQq2W6J_suZdqNd__BaaXokfZVq" \ + --accessToken "$ACCESS_TOKEN" \ + --scopes "openid AdobeID" + +echo -e "\e[32mDone!\e[0m" diff --git a/go.mod b/go.mod index 98c7a2a..6eaf84c 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ go 1.23.0 toolchain go1.26.2 require ( - github.com/adobe/ims-go v0.21.0 + github.com/adobe/ims-go v0.22.0 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 diff --git a/go.sum b/go.sum index c395e4e..917a7dc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/adobe/ims-go v0.21.0 h1:Y0uBT0Fho54ZXhVY0wvT6FUGrM3OEg4J4aJb0fUQdKQ= -github.com/adobe/ims-go v0.21.0/go.mod h1:zGpx0ylsumBjkgd8fYgzJ8+Ci/zFABiBTAxbCscsyR8= +github.com/adobe/ims-go v0.22.0 h1:fN9sUoET8ytkv58aLEDCaUUTyXwsIpRjoSNy87EkbwU= +github.com/adobe/ims-go v0.22.0/go.mod h1:zGpx0ylsumBjkgd8fYgzJ8+Ci/zFABiBTAxbCscsyR8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/ims/config_test.go b/ims/config_test.go index 5cbcab0..a82a9f4 100644 --- a/ims/config_test.go +++ b/ims/config_test.go @@ -295,7 +295,7 @@ func TestValidateAuthorizeClientCredentialsConfig(t *testing.T) { } } -func TestValidateRegisterConfig(t *testing.T) { +func TestValidateDCRConfig(t *testing.T) { tests := []struct { name string config Config @@ -308,7 +308,7 @@ func TestValidateRegisterConfig(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.config.validateRegisterConfig() + err := tt.config.validateDCRConfig() assertError(t, err, tt.wantErr) }) } diff --git a/ims/dcr.go b/ims/dcr.go new file mode 100644 index 0000000..a2eec68 --- /dev/null +++ b/ims/dcr.go @@ -0,0 +1,61 @@ +// Copyright 2026 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may obtain a copy +// of the License at http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software distributed under +// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +// OF ANY KIND, either express or implied. See the License for the specific language +// governing permissions and limitations under the License. + +// Dynamic Client Registration (DCR): POST JSON to IMS /ims/register. + +package ims + +import ( + "fmt" + + "github.com/adobe/ims-go/ims" +) + +const ( + dcrRegisterPath = "/ims/register" + maxDCRResponseBodySize = 1 << 20 // 1 MiB +) + +func (i Config) validateDCRConfig() error { + switch { + case i.URL == "": + return fmt.Errorf("missing IMS base URL parameter") + case !validateURL(i.URL): + return fmt.Errorf("invalid IMS base URL parameter") + case i.ClientName == "": + return fmt.Errorf("missing client name parameter") + case len(i.RedirectURIs) == 0: + return fmt.Errorf("missing redirect URIs parameter") + default: + return nil + } +} + +func (i Config) DCRRegister() (string, error) { + if err := i.validateDCRConfig(); err != nil { + return "", fmt.Errorf("invalid parameters for client registration: %w", err) + } + + c, err := i.newIMSClient() + if err != nil { + return "", fmt.Errorf("error creating the IMS client: %w", err) + } + + resp, err := c.DCR(&ims.DCRRequest{ + ClientName: i.ClientName, + RedirectURIs: i.RedirectURIs, + Scopes: i.Scopes, + }) + if err != nil { + return "", fmt.Errorf("error during client registration: %w", err) + } + + return string(resp.Body), nil +} diff --git a/ims/register.go b/ims/register.go deleted file mode 100644 index d8a6358..0000000 --- a/ims/register.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2025 Adobe. All rights reserved. -// This file is licensed to you under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. You may obtain a copy -// of the License at http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software distributed under -// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -// OF ANY KIND, either express or implied. See the License for the specific language -// governing permissions and limitations under the License. - -package ims - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strings" -) - -func (i Config) validateRegisterConfig() error { - switch { - case i.URL == "": - return fmt.Errorf("missing IMS base URL parameter") - case !validateURL(i.URL): - return fmt.Errorf("invalid IMS base URL parameter") - case i.ClientName == "": - return fmt.Errorf("missing client name parameter") - case len(i.RedirectURIs) == 0: - return fmt.Errorf("missing redirect URIs parameter") - default: - return nil - } -} - -// Register performs Dynamic Client Registration. -func (i Config) Register() (string, error) { - if err := i.validateRegisterConfig(); err != nil { - return "", fmt.Errorf("invalid parameters for client registration: %w", err) - } - - // Build the request payload using json.Marshal for proper escaping. - payload, err := json.Marshal(map[string]any{ - "client_name": i.ClientName, - "redirect_uris": i.RedirectURIs, - }) - if err != nil { - return "", fmt.Errorf("error building registration payload: %w", err) - } - - endpoint := strings.TrimRight(i.URL, "/") + "/ims/register" - - req, err := http.NewRequest("POST", endpoint, strings.NewReader(string(payload))) - if err != nil { - return "", fmt.Errorf("error creating request: %w", err) - } - req.Header.Add("content-type", "application/json") - - httpClient, err := i.httpClient() - if err != nil { - return "", fmt.Errorf("error creating the HTTP client: %w", err) - } - - res, err := httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("error making registration request: %w", err) - } - defer func() { _ = res.Body.Close() }() - - body, err := io.ReadAll(io.LimitReader(res.Body, 1<<20)) // 1 MB max - if err != nil { - return "", fmt.Errorf("error reading response body: %w", err) - } - - if res.StatusCode < 200 || res.StatusCode >= 300 { - return "", fmt.Errorf("registration failed with status %d: %s", res.StatusCode, string(body)) - } - - return string(body), nil -}