Automating creation of an Azure AD Web App Key - powershell

We've written a set of Powershell scripts to automate the process of creating new instances of our web app in our Azure environment. One part of these scripts uses the Graph API to create a few Azure AD objects, as well as corresponding objects in Auth0, which we use for single sign-on. So far, we haven't been successful in programmatically creating a new Key for the AAD Application. We've tried a number of methods, and while we can see that the resulting Application does have a Key in place (and the connection object in Auth0 has that key in its Client Secret field), we always receive this error when trying to authenticate:
By this point, we've already granted access to the app to single sign-on and to read directory data in AAD. (edit: We do this through manually following the provisioning_ticket_URL that is part of the object returned by the Auth0 API call to create the Connection object. This URL prompts us for Azure credentials and then displays this page:
)
This error persists until we manually create a new Key for the application through the Azure portal, save the application, and copy the newly created key into the Auth0 connection. Doing so always resolves the problem, but we'd like to avoid this extra step.
Within the body of the AAD application that we're creating in the API call, we have this section that defines the key:
"passwordCredentials":
[
{
"startDate": "2016-10-28T20:40:32Z",
"endDate": "2017-10-29T20:40:32Z",
"keyId": "(a GUID)",
"value": "(a Base64 string)"
}
],
As for those values, we've tried to generate them in quite a few different ways, and it accepts the values on the API PUT call, but they all still give us that same error when we try to log in. As an example, one way we tried was to plug in the values from $appKeyGUID and $appKeyValue from this:
$appKeyGUID = [guid]::NewGuid()
$guidBytes = [System.Text.Encoding]::UTF8.GetBytes($appKeyGUID)
$appKeyValue = [System.Convert]::ToBase64String($guidBytes);
into the keyID and value, respectively. I've read elsewhere that the value should be 44 characters long, though, which this one will not be.
But it seems like the value itself might not be the problem. I've tried generating a key through the Azure portal, using a Graph API GET call to retrieve the keyId, and then hardcoding those two exact values into the application body, but logging in still yields the same error.
Any idea where I'm going wrong?
EDIT: Per Philippe's suggestion, I tried changing only the Display Name of the AD application through the portal, and that did indeed resolve the problem. This led me to think that maybe something was wrong elsewhere within the application body that was being fixed when saving through the portal. I checked the manifest before and after doing that manual save, and there was indeed one small difference: within the RequiredResourceAccess section (of which I learned from here http://www.cloudidentity.com/blog/2015/09/01/azure-ad-permissions-summary-table/ and here https://www.microsoftpressstore.com/articles/article.aspx?p=2473127&seqNum=2), I had this:
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Role"
},
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Scope"
}
Instead of this, which the portal changes it to
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Role,Scope"
},
So I changed the body we're sending to match the second format. Unfortunately, we still are getting the same error with this change in place. Further, I verified that the manifest is now identical before and after making the save on the portal, as is the body returned by an API GET call on the application. There must be something else that the portal save is changing other than the application's definition.
After that, I tried using the Graph API to perform two PATCH calls to update the display name to something and then change it back, hoping that it would behave similarly to doing it through the portal and fix the problem. I verified through the portal that the PATCH calls were indeed changing the display name of the app. Sadly, it seems those edits didn't fix the issue, and I'm still getting the original error.
We create the application with a Graph API call like this:
$uri = "https://graph.windows.net/$waadTenant/applications?api-version=1.6"
$newADApp = (Invoke-RestMethod –Uri $uri –Headers $authHeader –Method POST -Body $newappbody –Verbose)
And here is the $newappbody that we end up using to define the application. I've left some things hard coded for troubleshooting purposes:
{
"odata.type": "Microsoft.DirectoryServices.Application",
"displayName": "customer1",
"homepage": "https://customer1.(our tenant).com",
"identifierUris":
[
"https://customer1.(our tenant).com"
],
"replyUrls":
[
"https://(our tenant).auth0.com/login/callback"
],
"passwordCredentials":
[
{
"startDate": "(a hardcoded date)",
"endDate": "(a hardcoded date)",
"keyId": "(hardcoded GUID that was previously generated by the portal and extracted through an API GET)",
"value": "(hardcoded Base64 like above)"
}
],
"requiredResourceAccess":
[
{
"resourceAppId": "00000002-0000-0000-c000-000000000000",
"resourceAccess":
[
{
"id": "5778995a-e1bf-45b8-affa-663a9f3f4d04",
"type": "Role,Scope"
},
{
"id": "311a71cc-e848-46a1-bdf8-97ff7156d8e6",
"type": "Scope"
},
{
"id": "6234d376-f627-4f0f-90e0-dff25c5211a3",
"type": "Scope"
}
]
}
]
}

It seems like the ultimate issue here is related to Application Consent.
I will go into more details here, but feel free to quickly take a read through this document which describes our Consent Framework:
https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/
The stuff you are showing in your Application manifest are related to your application configuration, however, the consent record for your app will not be found there, thus I think your 'a/b' testing will not be fruitful.
I think what you are really finding here is that the Azure Portal has some "magic" in the background which will consent to your application if you are an administrator and you save the application. When you register an application from start to finish in the portal (again, as an admin), we record consent for you automatically once you save the app. This happens in the background, and the resulting 'objects' that get created are: A service principal for your app in your tenant, and consent links between you as the user and your service principal. The link that gets created is specifically an 'admin tenant-wide consent' object, which is the same thing you would get if you added "prompt=admin_consent" to your login url as a query string.
When you create an application using other methods, like the Graph API or AAD PowerShell, these objects are not created, which will result in the kind of error you are seeing when you try and authenticate.
The solution here is that you will need to have an interactive login experience one time with an Admin of the tenant before you are able to get other people to sign into the application. During this login experience, you must have the admin consent to your app and the permissions it requires. After this one time consent experience, subsequent calls to/from your application should not require consent, and you will not run into the issue you are having.
So in summary:
Automatically create an App using whatever method you want
Then use your application information, and create an interactive login with the administrator, using the query string "prompt=admin_consent"
Then use your app however you expect it to work
Thanks!
Shawn Tabrizi

Related

Serverless Watson Deployment? (Questions about integrations with other RESTful services)

Introduction:
I feel like I'm missing something terribly obvious about how Watson Assistant should be designed at an architecture level, but maybe I'm not.
The specific problem I'm having is that I can't seem to get API calls for information back into the conversation.
The bigger issue is that I'm not sure I setting this all up correctly for the long-haul of what I'm trying to accomplish.
Purpose:
I am building a 24/7 customer-service Tier 1 helpdesk for our managed networks. A user of one of our networks should, via SMS, Web chat, Facebook messenger, and eventually phone call, be able to ask for:
Instructions on how to connect their specific device (PC, Mac, Chromebook, Xbox, Apple TV, etc.)
Ask for help troubleshooting if the instructions don't result in a successful connection. (Step by step instructions for deleting the saved network, restarting the wireless card, etc.)
Help creating a case - at which point the conversation becomes Watson asking for a bunch of information, like what time and date they first experienced the problem, any other times/dates they experienced the problem, their MAC address, etc. etc.
Problems:
I have most of the dialog built and working well. Getting information via Entities, saving to context variables, spitting them back out to make the conversation work, digressions, etc. all working.
I cannot, for the life of me, figure out what I am doing wrong when trying to GET information from an external API.
We have a 'daily password' for our guest networks, and we would like a user who asks for the daily password to receive it. This involves a very simple GET request to a publicly accessible server.
I have built a BlueMix/IBM Cloud function that works perfectly, but I can't seem to successfully call or receive information back from it.
Watson Error:
Error when updating output with output of dialog node id [node_66_xxxxxxxxxx]. Node output is [{"text":{"values":["Today's password for <? $guestNetwork.ssid ?> is <? $guestNetwork.password ?>"],"selection_policy":"sequential"}}] SpEL evaluation error: Expression [ $guestNetwork.ssid ] converted to [ context['guestNetwork'].ssid ] at position 0: EL1007E: Property or field 'ssid' cannot be found on null (and there is 1 more error in the log)
This error leads me to believe I am not properly defining the result variable in Watson, or improperly trying to retrieve it in conversation - because I do know my code returns the SSID and Password when I run it in BlueMix Console.
JSON for the action itself (and yes, I am setting the credentials in the previous node):
{
"output": {
"text": {
"values": [
"Today's password for <? $guestNetwork.ssid ?> is <? $guestNetwork.psk ?>"
],
"selection_policy": "sequential"
}
},
"actions": [
{
"name": "get-http-resource/getGuestNetworkPassword",
"type": "server",
"credentials": "$private.myCredentials",
"result_variable": "$guestNetwork"
}
]
}
Question:
Is my idea of a 'serverless' Watson possible by using Watson <-> IBM Cloud <-> external services? We don't currently have an 'application' or a server, it's all integrations between existing services.
Can anyone help me understand what I'm doing wrong when trying to access that variable?
Bonus points: How do I know to access the variable only after the action has completed successfully in IBM cloud? Basically, if accessing the information via the IBM Cloud function I wrote takes 1.5 seconds, do I need to pause the dialog for 1.5 seconds? Or am I completely missing the point for how to get external info in and out of Watson?
EDIT:
After watching Mitch's video, I have changed a couple things around, and the error message has evolved to this:
"Error when updating output with output of dialog node id
[node_66_1533646714776]. Node output is [{"text":{"values":["Today's
password for is "],"selection_policy":"sequential"}}] SpEL evaluation error:
Expression [ $guestNetwork['ssid'] ] converted to [
context['guestNetwork']['ssid'] ] at position 24: EL1012E: Cannot
index into a null value" error.
Without seeing your dialog, its a guess, but most common error I see is that you just need to jump to a child dialog node after doing the action call. You cannot do the action call and show the response in the same dialog node, as dialog needs a chance to run the action.
Its outlined in my video here:
https://ibm-dte.mybluemix.net/ibm-watson-assistant?refresh
see the video on dialog callouts. Its 13 minutes long I'm sure you only need about 2 of them, but still, should help.
What you are trying to do is definitely possible, especially if it works from within the cloud function environment.
We re-created the action using the default package (not having it in a sub-package), and it started working immediately.
Things to note: Watson dialog editor does not like dashes in the package name.
Thanks Mitch!

Why does an HTTP header sent to my Smart Home webhook indicate API version v1, when I am using v2?

Our project was created after May 2017, therefore we believe it is using the v2 Google Assistant API, in accordance with what the documentation says here.
However, the 'Google-Assistant-API-Version' header in HTTP requests sent to our webhook contains the value 'v1'.
Google-Assistant-API-Version: v1
All the content is using camelCase, rather than snake_case, however, so it does appear to be using v2. As far as we can tell, we are also responding using the v2 format, and this works fine.
We also include the 'Google-Assistant-API-Version' header in our responses with the value 'v2'. For testing purposes we have both removed this header entirely and changed its value to 'v1'. In both cases, the skill continues to function normally.
I have also attempted to change my action package to include the 'fulfillmentApiVersion' field (as described here), but it doesn't appear to have any effect.
{
"actions": [{
"name": "actions.devices",
"deviceControl": {},
"fulfillment": {
"conversationName": "automation"
}
}
],
"conversations": {
"automation": {
"name": "automation",
"url": "https://**************************",
"fulfillmentApiVersion": 2
}
}
}
While we are not experiencing any problems at this time, we wonder if this is a sign of some dormant error, and were wondering if anyone could offer some clarification?
The Google Assistant API version may be incorrect, or it may be referring to the API version for Smart Home. The Smart Home APIs launched publicly around May and use a different protocol for the data returned from the webhook.
If you are building a Smart Home application, please refer to the documentation above on how to write your webhook responses.

How to modify System.CreatedBy field in VSTS via API call

Apologies if this is very naive question. New to VSTS...
We are thinking to use VSTS for our daily project works. Currently, we are on old TFS hosted in-house. We have window application which is data entry tool and when any error generated Staff can raise support request via clicking one button which includes all traceback and any other necessary information for us to debug or they can raise new support work. We are thinking to integrate VSTS for our support work as well by calling API endpoint to create a task in VSTS.
I am thinking to use one service account in code that calls API to create a work item in VSTS however, I need to update System.CreatedBy field to the actual user rather than service account who raises this work item. I have seen numbers of other posts which says its read-only field.
The process is, I create a work item and then update CreatedBy field by calling following endpoint.
https://XXXXXX.visualstudio.com/DefaultCollection/_apis/wit/workitems/11?bypassRules=true&api-version=1.0
passing following JSON
[
{
"op": "replace",
"path": "/fields/System.CreatedBy",
"value": "Test, Mr <Test#example.com>"
},
]
This doesn't update the field.
Is there any way I can update this field? Thanks for your time.
Well, just read this https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/update?view=azure-devops-rest-6.0#update-a-link
NOTE: System.CreatedBy and System.CreatedDate can only be modified using bypass rules on work item creation, i.e. the first revision of a work item.

Facebook Ads API: How can I create ads preview without create campaign first?

I check a lot of documents, and flow like that:
Submit data to create Campaign, target, and AdCreative
Use ID of AdCreative to generate preview code
Display preview code to my site (to show to my user)
But I don't want to create Campaign, Targeting, and AdCreative before preview, just submit raw data of AdCreative to get preview code.
I found that doc https://developers.facebook.com/docs/graph-api/reference/generatepreviews/, and look like they can solve my problem... But it didn't work.
My test data is: https://graph.facebook.com/v2.6/generatepreviews?ad_format=RIGHT_COLUMN_STANDARD&creative={"object_story_spec":{"link_data":{"message":"msg","link":"http://kimkha.com?1","caption":"ccccc"}},"name":"NAME 1","body":"dddddd","title":"titititit","call_to_action_type":"OPEN_LINK","object_url":"http://kimkha.com"}&access_token=<token>
And the error:
{
"error": {
"message": "(#275) Cannot determine the target object for this request. Currently supported objects include ad account, business account and associated objects.",
"type": "OAuthException",
"code": 275,
"fbtrace_id": "GeckbxpU9gr"
}
}
I ran into the same problem and got past it, despite not being able to get a fully-functioning dynamic preview working. Here are some more pointers to help save others the pain.
The url needs to include an "ad account, business account or associated objects" like the error message states. For my case I used my ad account id (https://www.facebook.com/business/help/1492627900875762). The url changed to be the following base format: https://graph.facebook.com/v2.10/act_<your-app-id>/generatepreviews
When you get the response, you need to decode the body and you can use the url directly to test. I replaced \/ with /, & with & and removed the trailing slash. After this you get a url that should give you more specific error messages
I had to also specify a "page_id" parameter that the ad will be associated with. This is a sibling of "link_data" in the sample JSON listed above.
You may get error messages telling you to change the format of certain fields.
Eventually I got the error "Preview Not Available: Unable to display a preview of this ad. (fbtrace_id: Dsfql/z/qVI)" and finally lost the will to continue. The documentation is far from easy to follow and does not have clear examples.
I'm sorry I can't give a working solution, but for my case I was evaluating this API for a non-critical piece of work and timeboxing prevented me from proceeding. Hopefully I help save some time for someone else.
I ran into this problem. The solution for me was to use my app token instead of the Graph API Explorer's token. Hope this helps!
My two cents after struggling on this issue. My request had to be formatted as follows (this is using video data, but should extend to link data):
act_{ACT_ID}/generatepreviews
?ad_format=DESKTOP_FEED_STANDARD
&creative={object_story_spec:{
page_id:<PAGE_ID>,
video_data: {
image_url: <IMAGE_URL>,
call_to_action:{
type:"SHOP_NOW",
value:{
link: <URL>
}
}
}
}}
In this case, the call_to_action has to be placed inside the video_data (or link_data) parameter and has to be formatted as an object. I did also have to change & with &, as Matt mentions.

How do I list tabs on a standalone page?

I've created a page on Facebook without creating an account. This means that a default account is created with the page_id the same as the account_id.
When I try and perform a graph call to the tabs on the page:
/<page_id>/tabs?auth_token=<auth token>
I get an empty "data" representation:
{
"data": [
]
}
Even though there are application tabs installed.
Add the tab_id makes no difference, I get the same response.
I'm guessing the page is getting confused as the account but I cannot find any way to distinguish the difference between the account and the page.
Does anyone know if it is possible to retrieve the page information another way?
Double-check that the access token you're using is the Page Access Token - this is the most likely reason this will fail to return an answer