{"action":"created","comment":{"url":"https://api.github.com/repos/Azure/go-autorest/pulls/comments/50900909","id":50900909,"diff_hunk":"@@ -17,83 +18,128 @@ import (\n \n const resourceGroupURLTemplate = \"https://management.azure.com/subscriptions/{subscription-id}/resourcegroups\"\n const apiVersion = \"2015-01-01\"\n+const xplatClientID = \"04b07795-8ddb-461a-bbee-02f9e1bf7b46\"\n \n var (\n+\tmode           string\n+\ttenantID       string\n+\tsubscriptionID string\n+\tapplicationID  string\n+\n+\ttokenCachePath string\n+\n \tcertificatePath string\n-\tapplicationID   string\n-\ttenantID        string\n-\tsubscriptionID  string\n )\n \n func init() {\n+\tflag.StringVar(&mode, \"mode\", \"device\", \"mode of operation for SPT creation\")\n \tflag.StringVar(&certificatePath, \"certificatePath\", \"\", \"path to pk12/pfx certificate\")\n \tflag.StringVar(&applicationID, \"applicationId\", \"\", \"application id\")\n \tflag.StringVar(&tenantID, \"tenantId\", \"\", \"tenant id\")\n \tflag.StringVar(&subscriptionID, \"subscriptionId\", \"\", \"subscription id\")\n+\tflag.StringVar(&tokenCachePath, \"tokenCachePath\", \"\", \"location of oauth token cache\")\n+\n \tflag.Parse()\n \n-\tlog.Println(\"Using these settings:\")\n-\tlog.Println(\"* certificatePath:\", certificatePath)\n-\tlog.Println(\"* applicationID:\", applicationID)\n-\tlog.Println(\"* tenantID:\", tenantID)\n-\tlog.Println(\"* subscriptionID:\", subscriptionID)\n+\tlog.Printf(\"mode(%s) certPath(%s) appID(%s) tenantID(%s), subID(%s)\\n\",\n+\t\tmode, certificatePath, applicationID, tenantID, subscriptionID)\n \n-\tif strings.Trim(certificatePath, \" \") == \"\" ||\n-\t\tstrings.Trim(applicationID, \" \") == \"\" ||\n-\t\tstrings.Trim(tenantID, \" \") == \"\" ||\n+\tif strings.Trim(tenantID, \" \") == \"\" ||\n \t\tstrings.Trim(subscriptionID, \" \") == \"\" {\n-\t\tlog.Fatalln(\"Bad usage. Please specify all four parameters\")\n+\t\tlog.Fatalln(\"Bad usage. Please specify applicationID, tenantID, subscriptionID\")\n+\t}\n+\n+\tif mode != \"certificate\" && mode != \"cached\" && mode != \"device\" {\n+\t\tlog.Fatalln(\"Bad usage. Mode must be one of 'certificate', 'cached' or 'device'.\")\n+\t}\n+\n+\tif mode == \"device\" && applicationID == \"\" {\n+\t\tapplicationID = xplatClientID\n+\t}\n+\n+\tif mode == \"certificate\" && strings.Trim(certificatePath, \" \") == \"\" {\n+\t\tlog.Fatalln(\"Bad usage. Mode 'certificate' requires the 'certificatePath' argument.\")\n \t}\n }\n \n-func main() {\n-\tlog.Println(\"loading certificate... \")\n+func getSptFromCachedToken(clientID, tenantID, resource string) (*azure.ServicePrincipalToken, error) {\n+\ttoken, err := azure.LoadToken(tokenCachePath)\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"failed to load token from cache: %v\", err)\n+\t}\n+\n+\tspt := azure.NewServicePrincipalTokenFromManualToken(\n+\t\tclientID,\n+\t\ttenantID,\n+\t\tresource,\n+\t\t*token)\n+\n+\treturn spt, nil\n+}\n+\n+func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {\n+\tprivateKey, certificate, err := pkcs12.Decode(pkcs, password)\n+\tif err != nil {\n+\t\treturn nil, nil, err\n+\t}\n+\n+\trsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)\n+\tif !isRsaKey {\n+\t\treturn nil, nil, fmt.Errorf(\"PKCS#12 certificate must contain an RSA private key\")\n+\t}\n+\n+\treturn certificate, rsaPrivateKey, nil\n+}\n+\n+func getSptFromCertificate(clientID, tenantID, resource, certicatePath string) (*azure.ServicePrincipalToken, error) {\n \tcertData, err := ioutil.ReadFile(certificatePath)\n \tif err != nil {\n-\t\tlog.Fatalln(\"failed\", err)\n+\t\treturn nil, fmt.Errorf(\"failed to read the certificate file (%s): %v\", certificatePath, err)\n \t}\n \n \tcertificate, rsaPrivateKey, err := decodePkcs12(certData, \"\")\n \tif err != nil {\n-\t\tlog.Fatalln(\"failed\", err)\n+\t\treturn nil, fmt.Errorf(\"failed to decode pkcs12 certificate while creating spt\")\n \t}\n \n-\tlog.Println(\"retrieve oauth token... \")\n-\tspt, err := azure.NewServicePrincipalTokenFromCertificate(\n-\t\tapplicationID,\n+\tspt := azure.NewServicePrincipalTokenFromCertificate(\n+\t\tclientID,\n \t\tcertificate,\n \t\trsaPrivateKey,\n \t\ttenantID,\n \t\tazure.AzureResourceManagerScope)\n+\n+\treturn spt, nil\n+}\n+\n+func getSptFromDeviceFlow(clientID, tenantID, resource string) (*azure.ServicePrincipalToken, error) {","path":"autorest/azure/example/main.go","position":125,"original_position":125,"commit_id":"5651049584bf42172bacdad0f562638643ba3f32","original_commit_id":"5651049584bf42172bacdad0f562638643ba3f32","user":{"login":"colemickens","id":327028,"avatar_url":"https://avatars.githubusercontent.com/u/327028?v=3","gravatar_id":"","url":"https://api.github.com/users/colemickens","html_url":"https://github.com/colemickens","followers_url":"https://api.github.com/users/colemickens/followers","following_url":"https://api.github.com/users/colemickens/following{/other_user}","gists_url":"https://api.github.com/users/colemickens/gists{/gist_id}","starred_url":"https://api.github.com/users/colemickens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/colemickens/subscriptions","organizations_url":"https://api.github.com/users/colemickens/orgs","repos_url":"https://api.github.com/users/colemickens/repos","events_url":"https://api.github.com/users/colemickens/events{/privacy}","received_events_url":"https://api.github.com/users/colemickens/received_events","type":"User","site_admin":false},"body":"An SPT is just a convenience wrapper around a token. `getSptFromDeviceFlow` gets a token, and then creates the SPT wrapper and returns it.","created_at":"2016-01-26T21:14:55Z","updated_at":"2016-01-26T21:14:55Z","html_url":"https://github.com/Azure/go-autorest/pull/25#discussion_r50900909","pull_request_url":"https://api.github.com/repos/Azure/go-autorest/pulls/25","_links":{"self":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/comments/50900909"},"html":{"href":"https://github.com/Azure/go-autorest/pull/25#discussion_r50900909"},"pull_request":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/25"}}},"pull_request":{"url":"https://api.github.com/repos/Azure/go-autorest/pulls/25","id":57188048,"html_url":"https://github.com/Azure/go-autorest/pull/25","diff_url":"https://github.com/Azure/go-autorest/pull/25.diff","patch_url":"https://github.com/Azure/go-autorest/pull/25.patch","issue_url":"https://api.github.com/repos/Azure/go-autorest/issues/25","number":25,"state":"open","locked":false,"title":"OAuth2 Device Flow + Testing Coverage","user":{"login":"colemickens","id":327028,"avatar_url":"https://avatars.githubusercontent.com/u/327028?v=3","gravatar_id":"","url":"https://api.github.com/users/colemickens","html_url":"https://github.com/colemickens","followers_url":"https://api.github.com/users/colemickens/followers","following_url":"https://api.github.com/users/colemickens/following{/other_user}","gists_url":"https://api.github.com/users/colemickens/gists{/gist_id}","starred_url":"https://api.github.com/users/colemickens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/colemickens/subscriptions","organizations_url":"https://api.github.com/users/colemickens/orgs","repos_url":"https://api.github.com/users/colemickens/repos","events_url":"https://api.github.com/users/colemickens/events{/privacy}","received_events_url":"https://api.github.com/users/colemickens/received_events","type":"User","site_admin":false},"body":"1. Add support for the OAuth2 Device Flow\r\n\r\n2. Adds tests for the Device Flow\r\n\r\n3. Adds helpers and tests for persisting and restoring tokens\r\n\r\n4. Adds support for manual \"bring-your-own-tokens\" in ServicePrincipalToken\r\n\r\n5. Add tests for manual token and certificate based ServicePrincipalToken\r\n\r\n6. Updates the example with support for device auth, and persisting and restoring the token between executions\r\n\r\nThe code coverage in `autorest/azure` is now:\r\n* azure.go: `96.2%`\r\n* devicetoken.go: `100%`\r\n* persist.go: `92.0%`\r\n* token.go: `96.2%`\r\n\r\n","created_at":"2016-01-26T09:42:25Z","updated_at":"2016-01-26T21:14:55Z","closed_at":null,"merged_at":null,"merge_commit_sha":"64fadf5d83c9f52bc16fb16baa07068a48a9fb32","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Azure/go-autorest/pulls/25/commits","review_comments_url":"https://api.github.com/repos/Azure/go-autorest/pulls/25/comments","review_comment_url":"https://api.github.com/repos/Azure/go-autorest/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Azure/go-autorest/issues/25/comments","statuses_url":"https://api.github.com/repos/Azure/go-autorest/statuses/5651049584bf42172bacdad0f562638643ba3f32","head":{"label":"colemickens:oauth2-device-flow","ref":"oauth2-device-flow","sha":"5651049584bf42172bacdad0f562638643ba3f32","user":{"login":"colemickens","id":327028,"avatar_url":"https://avatars.githubusercontent.com/u/327028?v=3","gravatar_id":"","url":"https://api.github.com/users/colemickens","html_url":"https://github.com/colemickens","followers_url":"https://api.github.com/users/colemickens/followers","following_url":"https://api.github.com/users/colemickens/following{/other_user}","gists_url":"https://api.github.com/users/colemickens/gists{/gist_id}","starred_url":"https://api.github.com/users/colemickens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/colemickens/subscriptions","organizations_url":"https://api.github.com/users/colemickens/orgs","repos_url":"https://api.github.com/users/colemickens/repos","events_url":"https://api.github.com/users/colemickens/events{/privacy}","received_events_url":"https://api.github.com/users/colemickens/received_events","type":"User","site_admin":false},"repo":{"id":45519199,"name":"go-autorest","full_name":"colemickens/go-autorest","owner":{"login":"colemickens","id":327028,"avatar_url":"https://avatars.githubusercontent.com/u/327028?v=3","gravatar_id":"","url":"https://api.github.com/users/colemickens","html_url":"https://github.com/colemickens","followers_url":"https://api.github.com/users/colemickens/followers","following_url":"https://api.github.com/users/colemickens/following{/other_user}","gists_url":"https://api.github.com/users/colemickens/gists{/gist_id}","starred_url":"https://api.github.com/users/colemickens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/colemickens/subscriptions","organizations_url":"https://api.github.com/users/colemickens/orgs","repos_url":"https://api.github.com/users/colemickens/repos","events_url":"https://api.github.com/users/colemickens/events{/privacy}","received_events_url":"https://api.github.com/users/colemickens/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/colemickens/go-autorest","description":"This package implements an HTTP request pipeline suitable for use across multiple go-routines and provides the shared routines relied on by AutoRest (see https://github.com/Azure/autorest/) generated Go code.","fork":true,"url":"https://api.github.com/repos/colemickens/go-autorest","forks_url":"https://api.github.com/repos/colemickens/go-autorest/forks","keys_url":"https://api.github.com/repos/colemickens/go-autorest/keys{/key_id}","collaborators_url":"https://api.github.com/repos/colemickens/go-autorest/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/colemickens/go-autorest/teams","hooks_url":"https://api.github.com/repos/colemickens/go-autorest/hooks","issue_events_url":"https://api.github.com/repos/colemickens/go-autorest/issues/events{/number}","events_url":"https://api.github.com/repos/colemickens/go-autorest/events","assignees_url":"https://api.github.com/repos/colemickens/go-autorest/assignees{/user}","branches_url":"https://api.github.com/repos/colemickens/go-autorest/branches{/branch}","tags_url":"https://api.github.com/repos/colemickens/go-autorest/tags","blobs_url":"https://api.github.com/repos/colemickens/go-autorest/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/colemickens/go-autorest/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/colemickens/go-autorest/git/refs{/sha}","trees_url":"https://api.github.com/repos/colemickens/go-autorest/git/trees{/sha}","statuses_url":"https://api.github.com/repos/colemickens/go-autorest/statuses/{sha}","languages_url":"https://api.github.com/repos/colemickens/go-autorest/languages","stargazers_url":"https://api.github.com/repos/colemickens/go-autorest/stargazers","contributors_url":"https://api.github.com/repos/colemickens/go-autorest/contributors","subscribers_url":"https://api.github.com/repos/colemickens/go-autorest/subscribers","subscription_url":"https://api.github.com/repos/colemickens/go-autorest/subscription","commits_url":"https://api.github.com/repos/colemickens/go-autorest/commits{/sha}","git_commits_url":"https://api.github.com/repos/colemickens/go-autorest/git/commits{/sha}","comments_url":"https://api.github.com/repos/colemickens/go-autorest/comments{/number}","issue_comment_url":"https://api.github.com/repos/colemickens/go-autorest/issues/comments{/number}","contents_url":"https://api.github.com/repos/colemickens/go-autorest/contents/{+path}","compare_url":"https://api.github.com/repos/colemickens/go-autorest/compare/{base}...{head}","merges_url":"https://api.github.com/repos/colemickens/go-autorest/merges","archive_url":"https://api.github.com/repos/colemickens/go-autorest/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/colemickens/go-autorest/downloads","issues_url":"https://api.github.com/repos/colemickens/go-autorest/issues{/number}","pulls_url":"https://api.github.com/repos/colemickens/go-autorest/pulls{/number}","milestones_url":"https://api.github.com/repos/colemickens/go-autorest/milestones{/number}","notifications_url":"https://api.github.com/repos/colemickens/go-autorest/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/colemickens/go-autorest/labels{/name}","releases_url":"https://api.github.com/repos/colemickens/go-autorest/releases{/id}","deployments_url":"https://api.github.com/repos/colemickens/go-autorest/deployments","created_at":"2015-11-04T06:15:11Z","updated_at":"2016-01-15T03:21:09Z","pushed_at":"2016-01-26T09:37:10Z","git_url":"git://github.com/colemickens/go-autorest.git","ssh_url":"git@github.com:colemickens/go-autorest.git","clone_url":"https://github.com/colemickens/go-autorest.git","svn_url":"https://github.com/colemickens/go-autorest","homepage":"","size":3689,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"Azure:master","ref":"master","sha":"85ae902e47b41abe52bf867d773b9021a8d0f941","user":{"login":"Azure","id":6844498,"avatar_url":"https://avatars.githubusercontent.com/u/6844498?v=3","gravatar_id":"","url":"https://api.github.com/users/Azure","html_url":"https://github.com/Azure","followers_url":"https://api.github.com/users/Azure/followers","following_url":"https://api.github.com/users/Azure/following{/other_user}","gists_url":"https://api.github.com/users/Azure/gists{/gist_id}","starred_url":"https://api.github.com/users/Azure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Azure/subscriptions","organizations_url":"https://api.github.com/users/Azure/orgs","repos_url":"https://api.github.com/users/Azure/repos","events_url":"https://api.github.com/users/Azure/events{/privacy}","received_events_url":"https://api.github.com/users/Azure/received_events","type":"Organization","site_admin":false},"repo":{"id":39398382,"name":"go-autorest","full_name":"Azure/go-autorest","owner":{"login":"Azure","id":6844498,"avatar_url":"https://avatars.githubusercontent.com/u/6844498?v=3","gravatar_id":"","url":"https://api.github.com/users/Azure","html_url":"https://github.com/Azure","followers_url":"https://api.github.com/users/Azure/followers","following_url":"https://api.github.com/users/Azure/following{/other_user}","gists_url":"https://api.github.com/users/Azure/gists{/gist_id}","starred_url":"https://api.github.com/users/Azure/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Azure/subscriptions","organizations_url":"https://api.github.com/users/Azure/orgs","repos_url":"https://api.github.com/users/Azure/repos","events_url":"https://api.github.com/users/Azure/events{/privacy}","received_events_url":"https://api.github.com/users/Azure/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Azure/go-autorest","description":"This package implements an HTTP request pipeline suitable for use across multiple go-routines and provides the shared routines relied on by AutoRest (see https://github.com/Azure/autorest/) generated Go code.","fork":false,"url":"https://api.github.com/repos/Azure/go-autorest","forks_url":"https://api.github.com/repos/Azure/go-autorest/forks","keys_url":"https://api.github.com/repos/Azure/go-autorest/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Azure/go-autorest/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Azure/go-autorest/teams","hooks_url":"https://api.github.com/repos/Azure/go-autorest/hooks","issue_events_url":"https://api.github.com/repos/Azure/go-autorest/issues/events{/number}","events_url":"https://api.github.com/repos/Azure/go-autorest/events","assignees_url":"https://api.github.com/repos/Azure/go-autorest/assignees{/user}","branches_url":"https://api.github.com/repos/Azure/go-autorest/branches{/branch}","tags_url":"https://api.github.com/repos/Azure/go-autorest/tags","blobs_url":"https://api.github.com/repos/Azure/go-autorest/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Azure/go-autorest/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Azure/go-autorest/git/refs{/sha}","trees_url":"https://api.github.com/repos/Azure/go-autorest/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Azure/go-autorest/statuses/{sha}","languages_url":"https://api.github.com/repos/Azure/go-autorest/languages","stargazers_url":"https://api.github.com/repos/Azure/go-autorest/stargazers","contributors_url":"https://api.github.com/repos/Azure/go-autorest/contributors","subscribers_url":"https://api.github.com/repos/Azure/go-autorest/subscribers","subscription_url":"https://api.github.com/repos/Azure/go-autorest/subscription","commits_url":"https://api.github.com/repos/Azure/go-autorest/commits{/sha}","git_commits_url":"https://api.github.com/repos/Azure/go-autorest/git/commits{/sha}","comments_url":"https://api.github.com/repos/Azure/go-autorest/comments{/number}","issue_comment_url":"https://api.github.com/repos/Azure/go-autorest/issues/comments{/number}","contents_url":"https://api.github.com/repos/Azure/go-autorest/contents/{+path}","compare_url":"https://api.github.com/repos/Azure/go-autorest/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Azure/go-autorest/merges","archive_url":"https://api.github.com/repos/Azure/go-autorest/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Azure/go-autorest/downloads","issues_url":"https://api.github.com/repos/Azure/go-autorest/issues{/number}","pulls_url":"https://api.github.com/repos/Azure/go-autorest/pulls{/number}","milestones_url":"https://api.github.com/repos/Azure/go-autorest/milestones{/number}","notifications_url":"https://api.github.com/repos/Azure/go-autorest/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Azure/go-autorest/labels{/name}","releases_url":"https://api.github.com/repos/Azure/go-autorest/releases{/id}","deployments_url":"https://api.github.com/repos/Azure/go-autorest/deployments","created_at":"2015-07-20T17:33:08Z","updated_at":"2016-01-22T13:51:32Z","pushed_at":"2016-01-26T21:12:59Z","git_url":"git://github.com/Azure/go-autorest.git","ssh_url":"git@github.com:Azure/go-autorest.git","clone_url":"https://github.com/Azure/go-autorest.git","svn_url":"https://github.com/Azure/go-autorest","homepage":"","size":184,"stargazers_count":15,"watchers_count":15,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":9,"mirror_url":null,"open_issues_count":2,"forks":9,"open_issues":2,"watchers":15,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/25"},"html":{"href":"https://github.com/Azure/go-autorest/pull/25"},"issue":{"href":"https://api.github.com/repos/Azure/go-autorest/issues/25"},"comments":{"href":"https://api.github.com/repos/Azure/go-autorest/issues/25/comments"},"review_comments":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/25/comments"},"review_comment":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Azure/go-autorest/pulls/25/commits"},"statuses":{"href":"https://api.github.com/repos/Azure/go-autorest/statuses/5651049584bf42172bacdad0f562638643ba3f32"}}}}