OneDrive for Business API issues on Discovery and Authorization - azure

I've been trying to implement OneDrive Business API according to this OneDrive Release Notes. We've already implemented OneDrive end users API without any issue.
First obvious thing to do is to handle OAuth2 authentication to get right tokens, first to discover {tenant}-my.sharepoint.com specific Tenant OneDrive Business URI and after that get tokens for that tenant. Documenation for that purpose could be found here.
Following that tutorial we have been able to do the following:
Register the App in Azure AD. Done
Sign into OneDrive for Business
Log in and get an authorization code. Done
Redeem the authorization code for tokens. Done
Discover the OneDrive for Business resource URI. FAIL
Redeem refresh token for an access token to call OneDrive API. Done
Make a request to the OneDrive API. FAIL
It can be appreciated, we had issues on step 2, Discover the OneDrive for Business resource URI and Make a request to the OneDrive API.
Issue on Discover the OneDrive for Business resource URI
The problem with this part of the process is, although we are hitting to api.office.com/discovery/v2.0/me/services with the first Access Token obtained after redeem with resource api.office.com/discovery/, we are not getting in the list of services tenant specific sharepoint URI for OneDrive for Business. Any entry in the list we are getting come with capability = "MyFiles" AND serviceApiVersion = "v2.0" according documentation. In fact any entry in the list has the structure {tenant}-my.sharepoint.com in serviceEndpointUri. This is JSON response i am getting back. I removed some of the sensitive data:
{
"#odata.context" : "https://api.office.com/discovery/v2.0/me/$metadata#allServices",
"value" : [ {
"capability" : "Directory",
"providerName" : "Microsoft",
"serviceAccountType" : 2,
"serviceApiVersion" : "",
"serviceEndpointUri" : "http://azure.microsoft.com/",
"serviceName" : "Microsoft Azure",
"serviceResourceId" : null
}, {
"capability" : "MyFiles",
"providerName" : "Microsoft",
"serviceAccountType" : 2,
"serviceApiVersion" : "",
"serviceEndpointUri" : "http://www.microsoft.com/en-us/office365/online-software.aspx",
"serviceName" : "Office 365 SharePoint",
"serviceResourceId" : null
}, {
"capability" : "RootSite",
"providerName" : "Microsoft",
"serviceAccountType" : 2,
"serviceApiVersion" : "",
"serviceEndpointUri" : "http://www.microsoft.com/en-us/office365/online-software.aspx",
"serviceId" : "O365_SHAREPOINT",
"serviceName" : "Office 365 SharePoint",
"serviceResourceId" : null
}, {
"capability" : "MyFiles",
"providerName" : "Microsoft",
"serviceAccountType" : 1,
"serviceApiVersion" : "",
"serviceEndpointUri" : "https://g.live.com/8seskydrive/HomePageUrl",
"serviceName" : "OneDrive",
"serviceResourceId" : null
} ]
}
The problem with this is if i logged in to my portal.office.com and check my sharepoint urls, it is well configured and i can see {tenant}-my.sharepoint.com URI.
Issue on Make a request to the OneDrive API
Apart i am not able to discovery correct tenant sharepoint URI, if i hardcode the URL to redeem next Access Token request with my tenant sharepoint URI, I am getting an access token but when i want to make a call for example to https://{tenant}-my.sharepoint.com/drive/root or any other endpoint i am getting 401 Unauthorize response in every call, even when the token has just been acquired. Here is a handshake example. I am hiding sensitive data:
curl -v 'https://{tenant}-my.sharepoint.com/drives' -H 'Authorization: Bearer TOKEN_ACQUIRED'
Connected to {tenant}-my.sharepoint.com port 443
GET /drives HTTP/1.1
Host: {tenant}-my.sharepoint.com
Authorization: Bearer TOKEN_ACQUIRED
HTTP/1.1 401 Unauthorized
Could you advice me with this? Is there some configuration missing in my tenant? Is there some configuration missing in my Azure AD App?
BTW, permissions scope on my App i am getting in every redeem are AllSites.FullControl AllSites.Manage MyFiles.Write Sites.Search.All TermStore.ReadWrite.All User.Read.All. I think i have permissions properly set.
Best,
List item

Might be a bit late, but this blog article addresses how to use the OneDrive API and OneDrive for Business API exactly the same.
Here is a quick Java code snippet:
CloudRail.setAppKey("[CloudRail License Key]");
// CloudStorage cs = new OneDrive(redirectReceiver, "[clientIdentifier]", "[clientSecret]", "[redirectUri]", "[state]");
CloudStorage cs = new OneDriveBusiness(redirectReceiver, "[clientID]", "[clientSecret]", "[redirectUri]", "[state]");
new Thread() {
#Override
public void run() {
cs.createFolder("/TestFolder");
InputStream stream = null;
try {
stream = getClass().getResourceAsStream("Data.csv");
long size = new File(getClass().getResource("Data.csv").toURI()).length();
cs.upload("/TestFolder/Data.csv", stream, size, false);
} catch (Exception e) {
// TODO: handle error
} finally {
// TODO: close stream
}
}
}.start();

Related

Unable to create contact with additional id

I am currently using SDK version 3.39.0 and version 0004 of the API_MKT_CONTACT service definition to try to create a new Contact with an AdditionalID in Marketing Cloud with the following code:
ODataRequestUpdate contactRequest =
contactService
.updateContactOriginData(contact)
.withHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()))
.withHeader("Sap-Cuan-SequenceId", "UpdatePatch")
.withHeader("Sap-Cuan-SourceSystemType", "EXT")
.withHeader("Sap-Cuan-SourceSystemId", "sdk-test")
.toRequest();
var additionalId =
AdditionalID.builder()
.externalContactID(pii.getId().toString())
.originOfContact(origin)
.originOfContact_2("EMAIL") //ContactAdditionalOrigin
.externalContactID_2(pii.getEmail()) //ContactAdditionalID
.build();
var additionalIdRequest = contactService
.updateAdditionalIDs(additionalId)
.replacingEntity()
.withHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()))
.withHeader("Sap-Cuan-SourceSystemType", "EXT")
.withHeader("Sap-Cuan-SourceSystemId", "sdk-test")
.toRequest();
// use low level API as a work around for https://github.com/SAP/cloud-sdk/issues/156
ODataRequestBatch requestBatch = new ODataRequestBatch(ContactService.DEFAULT_SERVICE_PATH, ODataProtocol.V2);
requestBatch
.beginChangeset()
.addUpdate(contactRequest)
.addUpdate(additionalIdRequest)
.endChangeset();
HttpClient httpClient = HttpClientAccessor.getHttpClient(contactsDestination);
ODataRequestResultMultipartGeneric batchResult = requestBatch.execute(httpClient);
batchResult.getResult(additionalIdRequest);
This results in the following error:
{
"error": {
"code": "SY/530",
"message": {
"lang": "en",
"value": "Inline component is not defined or not allowed (HTTP PUT)"
},
"innererror": {
"application": {
"component_id": "CEC-MKT-DM-IC",
"service_namespace": "/SAP/",
"service_id": "API_MKT_CONTACT_SRV",
"service_version": "0004"
},
"transactionid": "3B63A2A6CC9205E0E00604E1D31F1CDF",
"timestamp": "20210315142401.8432680",
"Error_Resolution": {
"SAP_Transaction": "For backend administrators: use ADT feed reader \"SAP Gateway Error Log\" or run transaction /IWFND/ERROR_LOG on SAP Gateway hub system and search for entries with the timestamp above for more details",
"SAP_Note": "See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)",
"Batch_SAP_Note": "See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"
},
"errordetails": []
}
}
}
I am using this documentation as a guide for building my requests (under the section "Create Contacts with Additional IDs"). When I run the example code in Postman it works as expected. Note that the payload for the AdditionalIDs is an empty JSON object.
So I enabled HTTP wire logs and noticed that the SDK seems to be including the following payload:
PUT AdditionalIDs(ContactAdditionalOrigin='EMAIL',ContactAdditionalID='wade.watts#theoasis.com',ContactID='ae46e174-52a3-4de6-8caa-57213151b295',ContactOrigin='<CONTACT_ORIGIN>') HTTP/1.1
Sap-Cuan-SourceSystemId: sdk-test
Accept: application/json
Sap-Cuan-SourceSystemType: EXT
Content-Type: application/json
Sap-Cuan-RequestTimestamp: '2021-03-15T14:24:00.828'
{"ContactOrigin":"<CONTACT_ORIGIN>","ContactID":"ae46e174-52a3-4de6-8caa-57213151b295","ContactAdditionalOrigin":"EMAIL","ContactAdditionalID":"wade.watts#theoasis.com","ContactAdditionalIdUUID":null,"ContactUUID":null,"ContactAddlIDIsInvalid":null,"MarketingAreas":[]}
Unfortunately, I can't seem to find a way to omit the payload/inline component while using the SDK so that it matches the example code. Is this an issue with the SDK or am I doing something wrong? Any help would be much appreciated!
Cheers!
UPDATE
Applying the suggested workaround from #matkuhr I changed my additionalIdRequest above to this and it worked:
ODataEntityKey key = new ODataEntityKey(ODataProtocol.V2)
.addKeyProperty(AdditionalID.EXTERNAL_CONTACT_ID.getFieldName(), mcContact.getContactId())
.addKeyProperty(AdditionalID.ORIGIN_OF_CONTACT.getFieldName(), origin)
.addKeyProperty(AdditionalID.ORIGIN_OF_CONTACT_2.getFieldName(), "EMAIL")
.addKeyProperty(AdditionalID.EXTERNAL_CONTACT_I_D_2.getFieldName(), mcContact.getEmailAddress());
var request = new ODataRequestUpdate(
ContactService.DEFAULT_SERVICE_PATH,
"AdditionalIDs",
key,
"{}",
UpdateStrategy.REPLACE_WITH_PUT,
null,
ODataProtocol.V2);
request.addHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()));
request.addHeader("Sap-Cuan-SourceSystemType", "EXT");
request.addHeader("Sap-Cuan-SourceSystemId", "sdk-test);
It's not you doing something wrong, it's also not the SDK, it's the service. The service seems to substantially deviate from the OData V2 conventions as well as basic HTTP conventions.
You can work around this by leveraging the low-level APIs of the SDK even more. Create the update request fully manually with the payload the service requires, e.g.:
ODataEntityKey key = new ODataEntityKey(ODataProtocol.V2)
.addKeyProperty(Contact.XYZ.getFieldName(), contact.getXyz())
request = new ODataRequestUpdate(
contactService.getServicePath(),
contact.getEntityCollection(),
key,
"{ }", // this will be the payload
UpdateStrategy.REPLACE_WITH_PUT,
null,
ODataProtocol.V2);
request.addHeader("key", "val");
// add more headers & parameters if needed and execute

How to manage Azure DevOps group permissions using Rest API

I'm working on an automation task where I need a group to have a set of permissions on Repos, Pipelines, and releases, etc. I'm looking for a Rest API that can manage the permissions for this group.
For Example:
At Cross repo policies, how do I manage/set the permissions for the group "PROJECT ADMINISTRATORS" to allow the "Bypass policies when pushing", "Bypass policies when pushing", etc using a Rest API.
Thank you in Advance.
Based on your requirement, you could use the Rest API: Access Control Entries - Set Access Control Entries
POST https://dev.azure.com/{organization}/_apis/accesscontrolentries/{securityNamespaceId}?api-version=6.0
Request Body:
{
"token": "repoV2/{ProjectID}/{RepoID(If you want to set the permission for a single repo)}",
"merge": true,
"accessControlEntries": [
{
"descriptor": "Microsoft.TeamFoundation.Identity;S-....",
"allow": 32768,
"deny": 0,
"extendedinfo": {}
}
]
}
You can get the parameter values needed in the Rest API through the following methods:
securityNamespaceId:
GET https://dev.azure.com/{OrganizationName}/_apis/securitynamespaces?api-version=6.0
In the Response Body: you could search for the Git Repositories.
Then you could get the namespaceid and Parameter values corresponding to permissions.
For example:
To get the Group Identity(S-...), there is no directly Rest API to get it. You use the following method to get it:
1.Get the descriptor:
GET https://vssps.dev.azure.com/{org name}/_apis/graph/users?api-version=5.1-preview.1
2.Using the following C# code to convert it:
public static string Base64Decode(string base64EncodedData)
{
var lengthMod4 = base64EncodedData.Length % 4;
if (lengthMod4 != 0)
{
//fix Invalid length for a Base-64 char array or string
base64EncodedData += new string('=', 4 - lengthMod4);
}
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
Here has a blog which written by our Azure Identity Team engineer, you could refer to it for more detailed information.
In addition, you can also obtain the values of all parameters directly through the browser F12 record.

How to deploy Databricks cluster with specified permissions?

I am deploying some Databricks clusters using powershell script which takes as an input json file with pre-defined cluster templates, for example:
{
"cluster_name": "test1",
"max_retries": 1,
"spark_version": "5.3.x-scala2.11",
"timeout_seconds": 3600,
"autotermination_minutes": 60,
"node_type_id": "Standard_DS3_v2",
"driver_node_type_id": "Standard_DS3_v2",
"spark_env_vars": {
"PYSPARK_PYTHON": "/databricks/python3/bin/python3"
},
"spark_conf": {
"spark.databricks.delta.preview.enabled": "true"
},
"autoscale": {
"max_workers": 4,
"min_workers": 2
}
}
However, I would like to pre-assign to them some databricks permission groups. Can I do it using such cluster template? I cannot find any property that would allow me to specify those groups.
I can go to one of my clusters that has permissions assigned manually and export it as a json. However, in this case those are also missing from the template.
Thank you in advance!
The workaround that follows is so infinitely hacky, I wouldn't advise anyone to resort to this, if I knew another way. The workaround is to create a web session, log in, get a CSRF token, then issue a POST request to /acl/cluster/<cluster_id> with a map from user_ids to the requested permissions. Here's an example for setting all permissions on a single cluster for a single user (or group) using Python:
import json
import requests
DB_HOST = "db-cluster"
DB_USER = "user"
DB_PASS = "pass"
def change_acl(user_id, cluster_id):
host = DB_HOST
username = DB_USER
password = DB_PASS
session = requests.Session()
login_request = session.post("https://{}/j_security_check".format(host),
data={"j_username": username, "j_password": password})
if login_request.status_code >= 400:
raise Exception("login failed : {}".format(login_request.content))
config_request = session.get("https://{}/config".format(host))
if config_request.status_code >= 400:
raise Exception("config request failed : {}".format(config_request.content))
config = json.loads(config_request.content)
csrf_token = config['csrfToken']
acl_request = session.post(
"https://{}/acl/cluster/{}".format(host, cluster_id),
headers={
"X-CSRF-Token": csrf_token,
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
data=json.dumps({
"type": "set",
'permissions': {user_id: ["*"]}
})
)
if acl_request.status_code >= 400:
raise Exception("acl request failed : {}".format(acl_request.content))
If you find a better way, please let me know. The worst thing about this is you have to log in with username and password instead of a bearer token. The second worst thing is that this may break without any notice.
I hope the developers will find the time to implement this functionality in the near future.
Note: You cannot specify the permissions while creating a cluster using Clusters API . You should use "Group API" or "Admin Console"
Request structure of create cluster shown as follows:
Privileges can be granted to users or groups that are created via the groups API and Admin Console. Each user is uniquely identified by their username (which typically maps to their email address) in Databricks. Users who are workspace administrators in Databricks belong to a special admin role and can also access objects that they haven’t been given explicit access to.
Hope this helps.
If this answers your query, do click “Mark as Answer” and "Up-Vote" for the same. And, if you have any further query do let us know.

How to query the "Exposed Api" for all users calendars in a Tenant?

I want to be able to - from a crohn job on a 3rd. party server - query and modify all users in my the Tenants (organisation) calendars - but i just end up with a "Application ID URI" that i don't know what to do with.
No matter how i query the microsoft graph API i get "Invalid Audience" and the docs on the subject doesn't really seem to highlight how exactly to specify the correct "audience" or what a "resource" actually entails.
These are the steps i have taken in the Azure Active Directory portal:
I use the Client Credentials Grant flow so i can access with a Cron and not through a user. https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
1) Registered an application in the portal. So i get some ID's (anonymized here)
Application (client) ID
:
51ed7b6d-d33e-491e
Directory (tenant) ID
:
c181f4f3-912b-4acf-
Object ID
:
3f52a799-f2ab-4161-a81c
2) Created a secret so i can provide that together with the Application id to get a token.
3) Given "Api Application Permissions" to https://graph.microsoft.com/Calendars.ReadWrite.
4) "Exposed an Api" and called it's scope "readwrite calendars" so the Application ID URI with the scope end up being: api://51ed7b6d-d33e-491e-9d40-1/readwritecalendars
5) Authorized the API to the application with the Application (client) ID from step 1 so i don't need admin consent to query.
Problem is now i just end up with an "Application ID URI", okay how i do i exactly query my for the calendars then?
I am using https://github.com/TheNetworg/oauth2-azure here:
I am able to successfully get a token:
$provider = new \TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => env('OAUTH_APP_ID'),
'clientSecret' => env('OAUTH_APP_PASSWORD'),
'redirectUri' => 'http://www.google.dk'
]);
$provider->tenant = 'secret';
$token = $provider->getAccessToken('client_credentials', [
'resource' => 'https://graph.windows.net',
]);
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
$provider->resource = "https://graph.microsoft.com/";
return $token ;
Gives me:
{
"token_type": "Bearer",
"ext_expires_in": "3600",
"expires_on": "1562669834",
"not_before": "1562665934",
"resource": "https://graph.windows.net/",
"access_token": "longstringofnumbers",
"expires": 1562269224
}
There is no "Aud" or "Audience" field returned.
This i where i get confused, how exactly do i form a link, how do i say that i want to query all calendars with the token?
Looking at the outlook graph documentation it specifies i need to query like this (https://learn.microsoft.com/en-us/graph/api/user-list-calendars?view=graph-rest-1.0&tabs=http):
GET /me/calendars
This doesn't really make sense to me as "me" seems to imply a user, i am trying to query stuff in a tenant not tied to a specific user?
Anyway if i try to query like:
$provider->get('me/calendars',$token);
Or different combinations of this i just get:
Access token validation failure. Invalid audience.
To sum it all up: how do i actually query the API to list/modify calendars, what is the actual endpoint i have to hit? And where do i put the actual query?
Thanks in advance!
A result would be a JSON object instead of the error message. A successful return to the query. In this case a list of all users calendars, or a "successfully updated" after modifying a calendar.
In Azure AD you can specify "API Permissions" for an app registration. If you want to query all calendars with one access token you should use "Application permissions".
Point 4 is for other types of applications like Office Add-Ins.
For all request you can use the default Graph endpoint https://graph.microsoft.com/v1.0/. So if you wan't to query a user calendar you should use this:
GET https://graph.microsoft.com/v1.0/<UserId or UPN>/calendar
Got it working with:
$provider = new \TheNetworg\OAuth2\Client\Provider\Azure([
'clientId' => env('OAUTH_APP_ID'),
'clientSecret' => env('OAUTH_APP_PASSWORD'),
]);
$provider->tenant = env('AZURE_TENANT');
$provider->urlAPI = "https://graph.microsoft.com/v1.0/";
$provider->resource = "https://graph.microsoft.com/";
$token = $provider->getAccessToken('client_credentials', [
'resource' => 'https://graph.microsoft.com',
]);
$currentCalendarEvents = $provider->get('users/some#some.com/calendars/Calendar/events?$top=1000', $token);
The $provider->resource and urlapi has to be set before the request off course as written in the docs: https://github.com/TheNetworg/oauth2-azure#microsoft-graph

Find a Instagram Users ID for API Access (As of 2018)

The Problem
I have built an Instagram Feed on a friend's WordPress website to display the eight latest images from a specified feed.
I hit this endpoint to achieve this:
https://api.instagram.com/v1/users/{$ig_user_id}/media/recent/?access_token={$ig_access_token}
Up until now, I have used my own test instagram client to populate the feed.
We're gearing up to go live now, and I've attempted to replace the Instagram User ID and Access Token with that of my friend's instagram account. Whilst I can still generate an access token (using this tool # PixelUnion), I can no longer find a way to access the User ID.
There is an existing SO thread & solution here, which no longer seems to be valid:
Instagram how to get my user id from username?
The Question
How does one go about finding an Instagram accounts User ID since apparent restrictions to the Instagram API? (~ mid 2018)
My Instagram Feed Code (PHP)
<?php
function prettify_instagram_image($image_data) {
// Plucks just what we need from the data returned from the Instagram API
$pretty_data= (object)[
"id" => $image_data->id ? $image_data->id : false,
"src_url" => $image_data->images->standard_resolution->url ? $image_data->images->standard_resolution->url : false,
"ig_link" => $image_data->link ? $image_data->link : false,
"likes" => $image_data->likes->count ? $image_data->likes->count : false,
"comments" => $image_data->comments->count ? $image_data->comments->count : false
];
return $pretty_data;
}
$ig_user_id = get_field('instagram_user_id', 'option'); // Pulling IG account info from WP Admin Options
$ig_access_token = get_field('instagram_access_token', 'option');
$ig_api_root = 'https://api.instagram.com/v1/users/';
$ig_api_method = '/media/recent/';
$images_to_display = 8;
// https://api.instagram.com/v1/users/{user-id}/media/recent/?access_token=ACCESS-TOKEN
// Instagram API connection
$response = wp_remote_get( $ig_api_root . $ig_user_id . $ig_api_method . "?access_token=" . $ig_access_token );
// Instagram response is JSON encoded, let's convert it to an object
$instagram_response = !is_wp_error($response) ? json_decode( $response['body'] ) : false;
$instagram_images = ($instagram_response && $instagram_response->data) ? array_map("prettify_instagram_image", $instagram_response->data) : false;
?>
I've found that, providing you have an access token, you can perform the following request in your browser:
https://api.instagram.com/v1/users/self?access_token=[VALUE]
In fact, access token contain the User ID (the first segment of the token):
<user-id>.1677aaa.aaa042540a2345d29d11110545e2499

Resources