Skip to content
This repository has been archived by the owner on Mar 28, 2024. It is now read-only.

STEP 13 contacts notes entitlement #31

Merged
merged 10 commits into from
Dec 9, 2020
8 changes: 4 additions & 4 deletions appstoreconnect/appstoreconnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (c *Client) NewRequest(method, endpoint string, body interface{}) (*http.Re
endpoint = apiVersion + "/" + endpoint
u, err := c.BaseURL.Parse(endpoint)
if err != nil {
return nil, err
return nil, fmt.Errorf("parsing endpoint failed: %v", err)
}

var buf io.ReadWriter
Expand All @@ -111,13 +111,13 @@ func (c *Client) NewRequest(method, endpoint string, body interface{}) (*http.Re
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(body); err != nil {
return nil, err
return nil, fmt.Errorf("encoding body failed: %v", err)
}
}

req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
return nil, fmt.Errorf("preparing request failed: %v", err)
}

if body != nil {
Expand All @@ -127,7 +127,7 @@ func (c *Client) NewRequest(method, endpoint string, body interface{}) (*http.Re
if _, ok := c.client.(*http.Client); ok {
signedToken, err := c.ensureSignedToken()
if err != nil {
return nil, err
return nil, fmt.Errorf("ensuring JWT token failed: %v", err)
}
req.Header.Set("Authorization", "Bearer "+signedToken)
}
Expand Down
11 changes: 11 additions & 0 deletions appstoreconnect/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CapabilityType string
// CapabilityTypes ...
const (
Ignored CapabilityType = "-ignored-"
ProfileAttachedEntitlement CapabilityType = "-profile-attached-"
ICloud CapabilityType = "ICLOUD"
InAppPurchase CapabilityType = "IN_APP_PURCHASE"
GameCenter CapabilityType = "GAME_CENTER"
Expand Down Expand Up @@ -76,6 +77,16 @@ var ServiceTypeByKey = map[string]CapabilityType{
// does not appear on developer portal
"com.apple.developer.icloud-container-identifiers": Ignored,
"com.apple.developer.ubiquity-container-identifiers": Ignored,
// These are entitlements not supported via the API and this step,
// profile needs to be manually generated on Apple Developer Portal.
"com.apple.developer.contacts.notes": ProfileAttachedEntitlement,
"com.apple.developer.carplay-audio": ProfileAttachedEntitlement,
"com.apple.developer.carplay-communication": ProfileAttachedEntitlement,
"com.apple.developer.carplay-charging": ProfileAttachedEntitlement,
"com.apple.developer.carplay-maps": ProfileAttachedEntitlement,
"com.apple.developer.carplay-parking": ProfileAttachedEntitlement,
"com.apple.developer.carplay-quick-ordering": ProfileAttachedEntitlement,
"com.apple.developer.exposure-notification": ProfileAttachedEntitlement,
}

// CapabilitySettingAllowedInstances ...
Expand Down
3 changes: 2 additions & 1 deletion appstoreconnect/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"time"

jwt "github.com/dgrijalva/jwt-go"
Expand All @@ -18,7 +19,7 @@ func signToken(token *jwt.Token, privateKeyContent []byte) (string, error) {
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
return "", fmt.Errorf("failed to sign JWT token, private key format is invalid: %v", err)
}

privateKey, ok := key.(*ecdsa.PrivateKey)
Expand Down
27 changes: 26 additions & 1 deletion autoprovision/entitlements.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ func dataProtectionEquals(entVal string, cap appstoreconnect.BundleIDCapability)
return true, nil
}

// CanGenerateProfileWithEntitlements checks all entitlements, wheter they can be generated
func CanGenerateProfileWithEntitlements(entitlementsByBundleID map[string]serialized.Object) (bool, string, string) {
for bundleID, entitlements := range entitlementsByBundleID {
for entitlementKey, value := range entitlements {
if (Entitlement{entitlementKey: value}).IsProfileAttached() {
return false, entitlementKey, bundleID
}
}
}

return true, "", ""
}

// IsProfileAttached returns an error if an entitlement does not match a Capability but needs to be addded to the profile
// as an additional entitlement, after submitting a request to Apple.
func (e Entitlement) IsProfileAttached() bool {
if len(e) == 0 {
return false
}
entKey := serialized.Object(e).Keys()[0]

capType, ok := appstoreconnect.ServiceTypeByKey[entKey]
return ok && capType == appstoreconnect.ProfileAttachedEntitlement
}

// AppearsOnDeveloperPortal reports whether the given (project) Entitlement needs to be registered on Apple Developer Portal or not.
// List of services, to be registered: https://developer.apple.com/documentation/appstoreconnectapi/capabilitytype.
func (e Entitlement) AppearsOnDeveloperPortal() bool {
Expand All @@ -79,7 +104,7 @@ func (e Entitlement) AppearsOnDeveloperPortal() bool {
entKey := serialized.Object(e).Keys()[0]

capType, ok := appstoreconnect.ServiceTypeByKey[entKey]
return ok && capType != appstoreconnect.Ignored
return ok && capType != appstoreconnect.Ignored && capType != appstoreconnect.ProfileAttachedEntitlement
}

// Equal ...
Expand Down
73 changes: 73 additions & 0 deletions autoprovision/entitlements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package autoprovision_test
import (
"testing"

"github.com/bitrise-io/xcode-project/serialized"
"github.com/bitrise-steplib/steps-ios-auto-provision-appstoreconnect/autoprovision"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -100,3 +101,75 @@ func TestICloudContainers(t *testing.T) {
})
}
}

func TestCanGenerateProfileWithEntitlements(t *testing.T) {
tests := []struct {
name string
entitlementsByBundleID map[string]serialized.Object
want bool
godrei marked this conversation as resolved.
Show resolved Hide resolved
want1 string
want2 string
}{
{
name: "no entitlements",
entitlementsByBundleID: map[string]serialized.Object{
"com.bundleid": map[string]interface{}{},
},
want: true,
want1: "",
want2: "",
},
{
name: "contains unsupported entitlement",
entitlementsByBundleID: map[string]serialized.Object{
"com.bundleid": map[string]interface{}{
"com.entitlement-ignored": true,
"com.apple.developer.contacts.notes": true,
},
},
want: false,
want1: "com.apple.developer.contacts.notes",
want2: "com.bundleid",
},
{
name: "contains unsupported entitlement, multiple bundle IDs",
entitlementsByBundleID: map[string]serialized.Object{
"com.bundleid": map[string]interface{}{
"aps-environment": true,
},
"com.bundleid2": map[string]interface{}{
"com.entitlement-ignored": true,
"com.apple.developer.contacts.notes": true,
},
},
want: false,
want1: "com.apple.developer.contacts.notes",
want2: "com.bundleid2",
},
{
name: "all entitlements supported",
entitlementsByBundleID: map[string]serialized.Object{
"com.bundleid": map[string]interface{}{
"aps-environment": true,
},
},
want: true,
want1: "",
want2: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, got2 := autoprovision.CanGenerateProfileWithEntitlements(tt.entitlementsByBundleID)
godrei marked this conversation as resolved.
Show resolved Hide resolved
if got != tt.want {
t.Errorf("CanGenerateProfileWithEntitlements() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("CanGenerateProfileWithEntitlements() got1 = %v, want %v", got1, tt.want1)
}
if got2 != tt.want2 {
t.Errorf("CanGenerateProfileWithEntitlements() got2 = %v, want %v", got2, tt.want2)
}
})
}
}
31 changes: 30 additions & 1 deletion bitrise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ app:
- BITRISE_CERTIFICATE_URL: $BITRISE_CERTIFICATE_URL
- BITRISE_CERTIFICATE_PASSPHRASE: $BITRISE_CERTIFICATE_PASSPHRASE
- TEAM_ID: $TEAM_ID
- KEYCHAIN_PATH: $HOME/Library/Keychains/login.keychain
- BITRISE_KEYCHAIN_PASSWORD: $BITRISE_KEYCHAIN_PASSWORD

# Used for development
- LOCAL_SAMPLE_PATH: $LOCAL_SAMPLE_PATH
- LOCAL_PROJECT_NAME: $LOCAL_PROJECT_NAME
- LOCAL_SCHEME: $LOCAL_SCHEME
- $LOCAL_KEYCHAIN_PATH: $LOCAL_KEYCHAIN_PATH

- API_TOKEN: $API_TOKEN
- APP_SLUG: $APP_SLUG

Expand Down Expand Up @@ -58,6 +65,18 @@ workflows:
- test_tvos_managed
- test_tvos_development_managed

debug:
envs:
- SAMPLE_APP_URL: $LOCAL_SAMPLE_PATH
- BITRISE_PROJECT_PATH: $LOCAL_PROJECT_NAME
- BITRISE_SCHEME: $LOCAL_SCHEME
- BITRISE_CONFIGURATION: Debug
- DISTRIBUTION_TYPE: development
- GENERATE_PROFILES: "yes"
- KEYCHAIN_PATH: $LOCAL_KEYCHAIN_PATH
after_run:
- _common

test_bundle_id:
envs:
- SAMPLE_APP_URL: https://github.com/bitrise-samples/sample-apps-ios-simple-objc.git
Expand Down Expand Up @@ -190,6 +209,8 @@ workflows:
- _common

_common:
envs:
- KEYCHAIN_PATH: $KEYCHAIN_PATH
steps:
- script:
title: Cleanup _tmp dir
Expand All @@ -209,7 +230,13 @@ workflows:
inputs:
- content: |-
#!/bin/bash
git clone -b $BRANCH $SAMPLE_APP_URL .
set -ex
MATCH_PATTERN="^https"
if [[ $SAMPLE_APP_URL =~ $MATCH_PATTERN ]]; then
git clone -b $BRANCH $SAMPLE_APP_URL .
else
cp -r $SAMPLE_APP_URL .
fi
- cocoapods-install:
run_if: '{{enveq "INSTALL_PODS" "true"}}'
title: CocoaPods install
Expand All @@ -225,6 +252,8 @@ workflows:
- project_path: $BITRISE_PROJECT_PATH
- scheme: $BITRISE_SCHEME
- configuration: $BITRISE_CONFIGURATION
- keychain_path: $KEYCHAIN_PATH
- keychain_password: $BITRISE_KEYCHAIN_PASSWORD
- verbose_log: "yes"
- script:
title: Output test
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ func main() {
log.Printf("- %s", id)
}

if ok, entitlement, bundleID := autoprovision.CanGenerateProfileWithEntitlements(entitlementsByBundleID); !ok {
log.Errorf("Can not create profile with unsupported entitlement (%s) for the bundle ID %s, due to API limitations.", entitlement, bundleID)
godrei marked this conversation as resolved.
Show resolved Hide resolved
failf("Please generate provisioning profile manually on Apple Developer Portal and use the Certificate and profile installer Step instead.")
}

platform, err := projHelper.Platform(config)
if err != nil {
failf("Failed to read project platform: %s", err)
Expand Down