diff --git a/cmd/profile.go b/cmd/profile.go index c9584a5..39e9e74 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -38,6 +38,7 @@ func profileCmd(imsConfig *ims.Config) *cobra.Command { cmd.Flags().StringVarP(&imsConfig.AccessToken, "accessToken", "t", "", "Access token.") cmd.Flags().StringVarP(&imsConfig.ProfileApiVersion, "profileApiVersion", "a", "v1", "Profile API version.") + cmd.Flags().BoolVarP(&imsConfig.DecodeFulfillableData, "decodeFulfillableData", "d", false, "Decode the fulfillable_data in the product context.") return cmd } diff --git a/ims/config.go b/ims/config.go index 4a2de34..4b8e196 100644 --- a/ims/config.go +++ b/ims/config.go @@ -15,33 +15,34 @@ import ( ) type Config struct { - URL string - ClientID string - ClientSecret string - PrivateKeyPath string - Organization string - Account string - Scopes []string - Metascopes []string - AccessToken string - RefreshToken string - DeviceToken string - ServiceToken string - AuthorizationCode string - ProfileApiVersion string - OrgsApiVersion string - Timeout int - ProxyURL string - ProxyIgnoreTLS bool - PublicClient bool - UserID string - Cascading bool - Token string - Port int - PKCE bool - FullOutput bool - Guid string - AuthSrc string + URL string + ClientID string + ClientSecret string + PrivateKeyPath string + Organization string + Account string + Scopes []string + Metascopes []string + AccessToken string + RefreshToken string + DeviceToken string + ServiceToken string + AuthorizationCode string + ProfileApiVersion string + OrgsApiVersion string + Timeout int + ProxyURL string + ProxyIgnoreTLS bool + PublicClient bool + UserID string + Cascading bool + Token string + Port int + PKCE bool + FullOutput bool + Guid string + AuthSrc string + DecodeFulfillableData bool } // Access token information diff --git a/ims/jwt_exchange.go b/ims/jwt_exchange.go index f05aabd..c152940 100644 --- a/ims/jwt_exchange.go +++ b/ims/jwt_exchange.go @@ -35,7 +35,7 @@ func (i Config) AuthorizeJWTExchange() (TokenInfo, error) { key, err := ioutil.ReadFile(i.PrivateKeyPath) if err != nil { - return TokenInfo{}, fmt.Errorf("read private key file: %v", err) + return TokenInfo{}, fmt.Errorf("error read private key file: %s, %v", i.PrivateKeyPath, err) } // Metascopes are passed as generic claims with the format map[string]interface{} diff --git a/ims/profile.go b/ims/profile.go index 73d1d14..d21108f 100644 --- a/ims/profile.go +++ b/ims/profile.go @@ -11,8 +11,14 @@ package ims import ( + "bytes" + "compress/gzip" + "encoding/base64" + "encoding/json" "fmt" + "io" "log" + "strings" "github.com/adobe/ims-go/ims" ) @@ -64,5 +70,94 @@ func (i Config) GetProfile() (string, error) { return "", err } - return string(profile.Body), nil + if !i.DecodeFulfillableData { + return string(profile.Body), nil + } + + // Decode the fulfillable_data in the product context + decodedProfile, err := decodeProfile(profile.Body) + if err != nil { + return "", err + } + return decodedProfile, nil +} + +func decodeProfile(profile []byte) (string, error) { + // Parse the profile JSON + var p map[string]interface{} + err := json.Unmarshal(profile, &p) + if err != nil { + return "", fmt.Errorf("error parsing profile JSON: %v", err) + } + findFulfillableData(p) + + modifiedJson, err := json.Marshal(p) + if err != nil { + return "", fmt.Errorf("error marshaling JSON during profile decode: %v", err) + } + + return string(modifiedJson), nil +} + +func findFulfillableData(data interface{}) { + switch data := data.(type) { + case map[string]interface{}: + for key, value := range data { + if key == "fulfillable_data" { + serviceCode, ok := data["serviceCode"].(string) + if ok && (serviceCode == "dma_media_library" || + serviceCode == "dma_aem_cloud" || + serviceCode == "dma_aem_contenthub" || + serviceCode == "dx_genstudio") { + + decodedFulfillableData, err := modifyFulfillableData(value.(string)) + if err != nil { + fmt.Printf("Error decoding fulfillable_data: %v", err) + return + } + data["fulfillable_data"] = decodedFulfillableData + } + } else { + findFulfillableData(value) + } + } + case []interface{}: + for _, item := range data { + findFulfillableData(item) + } + } +} + +type fulfillableData struct { + Iid string `json:"iid"` +} + +func modifyFulfillableData(data string) (string, error) { + strippedGzippedInstanceID := strings.Replace(data, "\"", "", 2) + gzippedInstanceIDBytes, err := base64.StdEncoding.DecodeString(strippedGzippedInstanceID) + if err != nil { + return "", fmt.Errorf("unable to base64 decode fulfillable_data: %v", err) + } + + gzipReader, err := gzip.NewReader(bytes.NewReader(gzippedInstanceIDBytes)) + if err != nil { + return "", fmt.Errorf("unable to create gzip reader: %v", err) + } + defer func() { + if _, gzErr := io.Copy(io.Discard, gzipReader); gzErr != nil { + fmt.Errorf("error while consuming the gzip reader: %v", gzErr) + } + + if gzErr := gzipReader.Close(); gzErr != nil { + fmt.Errorf("unable to close gzip reader: %v", gzErr) + } + }() + + iidDecoder := json.NewDecoder(gzipReader) + instanceIdJson := fulfillableData{} + err = iidDecoder.Decode(&instanceIdJson) + if err != nil { + return "", fmt.Errorf("unable to unmarshall the fulfillable_data: %v", err) + } + return instanceIdJson.Iid, nil }