I want to get the auth token from keystone using horizon and then wants to pass that auth token to my backed code.
i don't know how to get this, please help me out.
I read many articles and blogs blogs but i am not able to find the answer. Please just point me into the right direction.
Easiest is to use a Rest client to login and just take the token from the response. I like the Firefox RESTClient add-on but you can use any client you want.
Post a request to the Openstack Identity URL:
POST keystone_ip:port/version/tokens
(e.g. 127.0.0.1:5000/v2.0/tokens)
with header:
Content-Type: application/json
and body:
{
"auth": {
"tenantName": "enter_your_tenantname",
"passwordCredentials": {
"username": "enter_your_username",
"password": "enter_your_password"
}
}
}
Note: If you're not sure what is the correct identity (keystone) URL you can log in manually to Horizon and look for a list of API endpoints.
The response body will include something like this:
{
"token": {
"issued_at": "2014-02-25T08:34:56.068363",
"expires": "2014-02-26T08:34:55Z",
"id": "529e3a0e1c375j498315c71d08134837"
}
}
Use the returned token id as a header in new rest calls. For example, to get a list of servers use request:
GET compute_endpoint_ip:port/v2/tenant_id/servers
with Headers:
X-Auth-Token: 529e3a0e1c375j498315c71d08134837
Content-Type: application/json
As an example of how to get at it:
import keystoneclient.v2_0.client as ksclient
# authenticate with keystone to get a token
keystone = ksclient.Client(auth_url="http://192.168.10.5:35357/v2.0",
username="admin",
password="admin",
tenant_name="admin")
token = keystone.auth_ref['token']['id']
# use this token for whatever other services you are accessing.
print token
You can use python-keystoneclient. To authenticate the user, use for example
username='admin'
password='1234'
tenant_name='admin'
auth_url='http://127.0.0.1:5000/v2.0'
keystone = client.Client(username=username, password=password, tenant_name=tenant_name, auth_url=auth_url)
Once, the user is authenticated, a token is generated. The auth_ref property on the client ( keystone variable in this example) will give you a dictionary like structure having all the information you need about the token, which will enable you to re-use the token or pass it to the back-end in your case.
token_dict = keystone.auth_ref
Now,the token_dict is the variable that you can pass to your back-end.
Go to the node where you have installed Keystone services. Open vi /etc/keystone/keystone.conf
Check for the third line starting admin_token. It should be a long random string:
admin_token = 05131394ad6b49c56f217
That is your keystone token. Using python:
>>> from keystoneclient.v2_0.client as ksclient
>>> keystone = ksclient.Client(auth_url="http://service-stack.linxsol.com:35357/v2.0", username="admin", password="123456", tenant_name="admin")
Ofcourse, you will change auth_url, *username, password* and tenant_name to your choice. Now you can use keystone to execute all the api tasks:
keystone.tenants.list()
keystone.users.list()
keystone.roles.list()
Or use dir(keystone) to list all the available options.
You can reuse the token as follows:
auth_ref = keystone.auth_ref or token = ksclient.get_raw_token_from_identity_service(auth_url="http://service-stack.linxsol.com:35357/v2.0", username="admin", password="123456", tenant_name="admin")
But remember it returns a dictionary and a raw token not in a form of a token as you can see above.
For further information please check the python-keystoneclient.
I hope that helps.
Use the python-keystoneclient package.
Look into the Client.get_raw_token_from_identity_service method.
First you have to install python-keystoneclient.
To generate the token you can use the following code, here I want to mention you can change the authentication url with your server url but port number will be same,
from keystoneclient.v2_0 import client
username='admin'
password='1234'
tenant_name='demo'
auth_url='http://10.0.2.15:5000/v2.0' # Or auth_url='http://192.168.206.133:5000/v2.0'
if your username, password, or tenant_name is wrong then you will get keystoneclient.openstack.common.apiclient.exceptions.Unauthorized: Invalid user / password
keystone = client.Client(username=username, password=password, tenant_name=tenant_name, auth_url=auth_url)
token_dict = keystone.auth_ref
token_dict
Related
I'm trying to upgrade a legacy mail bot to authenticate via Oauth2 instead of Basic authentication, as it's now deprecated two days from now.
The document states applications can retain their original logic, while swapping out only the authentication bit
Application developers who have built apps that send, read, or
otherwise process email using these protocols will be able to keep the
same protocol, but need to implement secure, Modern authentication
experiences for their users. This functionality is built on top of
Microsoft Identity platform v2.0 and supports access to Microsoft 365
email accounts.
Note I've explicitly chosen the client credentials flow, because the documentation states
This type of grant is commonly used for server-to-server interactions
that must run in the background, without immediate interaction with a
user.
So I've got a python script that retrieves an Access Token using the MSAL python library. Now I'm trying to authenticate with the IMAP server, using that Access Token. There's some existing threads out there showing how to connect to Google, I imagine my case is pretty close to this one, except I'm connecting to a Office 365 IMAP server. Here's my script
import imaplib
import msal
import logging
app = msal.ConfidentialClientApplication(
'client-id',
authority='https://login.microsoftonline.com/tenant-id',
client_credential='secret-key'
)
result = app.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])
def generate_auth_string(user, token):
return 'user=%s\1auth=Bearer %s\1\1' % (user, token)
# IMAP time!
mailserver = 'outlook.office365.com'
imapport = 993
M = imaplib.IMAP4_SSL(mailserver,imapport)
M.debug = 4
M.authenticate('XOAUTH2', lambda x: generate_auth_string('user#mydomain.com', result['access_token']))
print(result)
The IMAP authentication is failing and despite setting M.debug = 4, the output isn't very helpful
22:56.53 > b'DBDH1 AUTHENTICATE XOAUTH2'
22:56.53 < b'+ '
22:56.53 write literal size 2048
22:57.84 < b'DBDH1 NO AUTHENTICATE failed.'
22:57.84 NO response: b'AUTHENTICATE failed.'
Traceback (most recent call last):
File "/home/ubuntu/mini-oauth.py", line 21, in <module>
M.authenticate("XOAUTH2", lambda x: generate_auth_string('user#mydomain.com', result['access_token']))
File "/usr/lib/python3.10/imaplib.py", line 444, in authenticate
raise self.error(dat[-1].decode('utf-8', 'replace'))
imaplib.IMAP4.error: AUTHENTICATE failed.
Any idea where I might be going wrong, or how to get more robust information from the IMAP server about why the authentication is failing?
Things I've looked at
Note this answer no longer works as the suggested scopes fail to generate an Access Token.
The client credentials flow seems to mandate the https://graph.microsoft.com/.default grant. I'm not sure if that includes the scope required for the IMAP resource
https://outlook.office.com/IMAP.AccessAsUser.All?
Verified the code lifted from the Google thread produces the SASL XOAUTH2 string correctly, per example on the MS docs
import base64
user = 'test#contoso.onmicrosoft.com'
token = 'EwBAAl3BAAUFFpUAo7J3Ve0bjLBWZWCclRC3EoAA'
xoauth = "user=%s\1auth=Bearer %s\1\1" % (user, token)
xoauth = xoauth.encode('ascii')
xoauth = base64.b64encode(xoauth)
xoauth = xoauth.decode('ascii')
xsanity = 'dXNlcj10ZXN0QGNvbnRvc28ub25taWNyb3NvZnQuY29tAWF1dGg9QmVhcmVyIEV3QkFBbDNCQUFVRkZwVUFvN0ozVmUwYmpMQldaV0NjbFJDM0VvQUEBAQ=='
print(xoauth == xsanity) # prints True
This thread seems to suggest multiple tokens need to be fetched, one for graph, then another for the IMAP connection; could that be what I'm missing?
Try the below steps.
For Client Credentials Flow you need to assign “Application permissions” in the app registration, instead of “Delegated permissions”.
Add permission “Office 365 Exchange Online / IMAP.AccessAsApp” (application).
Grant admin consent to you application
Service Principals and Exchange.
Once a service principal is registered with Exchange Online, administrators can run the Add-Mailbox Permission cmdlet to assign receive permissions to the service principal.
Use scope 'https://outlook.office365.com/.default'.
Now you can generate the SALS authentication string by combining this access token and the mailbox username to authenticate with IMAP4.
#Python code
def get_access_token():
tenantID = 'abc'
authority = 'https://login.microsoftonline.com/' + tenantID
clientID = 'abc'
clientSecret = 'abc'
scope = ['https://outlook.office365.com/.default']
app = ConfidentialClientApplication(clientID,
authority=authority,
client_credential = clientSecret)
access_token = app.acquire_token_for_client(scopes=scope)
return access_token
def generate_auth_string(user, token):
auth_string = f"user={user}\1auth=Bearer {token}\1\1"
return auth_string
#IMAP AUTHENTICATE
imap = imaplib.IMAP4_SSL(imap_host, 993)
imap.debug = 4
access_token = get_access_token_to_authenticate_imap()
imap.authenticate("XOAUTH2", lambda x:generate_auth_string(
'useremail',
access_token['access_token']))
imap.select('inbox')
The imaplib.IMAP4.error: AUTHENTICATE failed Error occured because one point in the documentation is not that clear.
When setting up the the Service Principal via Powershell you need to enter the App-ID and an Object-ID. Many people will think, it is the Object-ID you see on the overview page of the registered App, but its not!
At this point you need the Object-ID from "Azure Active Directory -> Enterprise Applications --> Your-App --> Object-ID"
New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
Microsoft says:
The OBJECT_ID is the Object ID from the Overview page of the
Enterprise Application node (Azure Portal) for the application
registration. It is not the Object ID from the Overview of the App
Registrations node. Using the incorrect Object ID will cause an
authentication failure.
Ofcourse you need to take care for the API-permissions and the other stuff, but this was for me the point.
So lets go trough it again, like it is explained on the documentation page.
Authenticate an IMAP, POP or SMTP connection using OAuth
Register the Application in your Tenant
Setup a Client-Key for the application
Setup the API permissions, select the APIs my organization uses tab and search for "Office 365 Exchange Online" -> Application permissions -> Choose IMAP and IMAP.AccessAsApp
Setup the Service Principal and full access for your Application on the mailbox
Check if IMAP is activated for the mailbox
Thats the code I use to test it:
import imaplib
import msal
import pprint
conf = {
"authority": "https://login.microsoftonline.com/XXXXyourtenantIDXXXXX",
"client_id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX", #AppID
"scope": ['https://outlook.office365.com/.default'],
"secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-Value
"secret-id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-ID
}
def generate_auth_string(user, token):
return f"user={user}\x01auth=Bearer {token}\x01\x01"
if __name__ == "__main__":
app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
client_credential=conf['secret'])
result = app.acquire_token_silent(conf['scope'], account=None)
if not result:
print("No suitable token in cache. Get new one.")
result = app.acquire_token_for_client(scopes=conf['scope'])
if "access_token" in result:
print(result['token_type'])
pprint.pprint(result)
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
imap = imaplib.IMAP4('outlook.office365.com')
imap.starttls()
imap.authenticate("XOAUTH2", lambda x: generate_auth_string("target_mailbox#example.com", result['access_token']).encode("utf-8"))
After setting up the Service Principal and giving the App full access on the mailbox, wait 15 - 30 minutes for the changes to take effect and test it.
Try with this script:
import json
import msal
import requests
client_id = '***'
client_secret = '***'
tenant_id = '***'
authority = f"https://login.microsoftonline.com/{tenant_id}"
app = msal.ConfidentialClientApplication(
client_id=client_id,
client_credential=client_secret,
authority=authority)
scopes = ["https://graph.microsoft.com/.default"]
result = None
result = app.acquire_token_silent(scopes, account=None)
if not result:
print(
"No suitable token exists in cache. Let's get a new one from Azure Active Directory.")
result = app.acquire_token_for_client(scopes=scopes)
# if "access_token" in result:
# print("Access token is " + result["access_token"])
if "access_token" in result:
userId = "***"
endpoint = f'https://graph.microsoft.com/v1.0/users/{userId}/sendMail'
toUserEmail = "***"
email_msg = {'Message': {'Subject': "Test Sending Email from Python",
'Body': {'ContentType': 'Text', 'Content': "This is a test email."},
'ToRecipients': [{'EmailAddress': {'Address': toUserEmail}}]
},
'SaveToSentItems': 'true'}
r = requests.post(endpoint,
headers={'Authorization': 'Bearer ' + result['access_token']}, json=email_msg)
if r.ok:
print('Sent email successfully')
else:
print(r.json())
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
Source: https://kontext.tech/article/795/python-send-email-via-microsoft-graph-api
I am trying to send an http post request to my google vertex ai endpoint for prediction. Though I do set the Bearer Token in the request header, the request still fails with the below error:
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED",
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ACCESS_TOKEN_TYPE_UNSUPPORTED",
"metadata": {
"service": "aiplatform.googleapis.com",
"method": "google.cloud.aiplatform.v1.PredictionService.Predict"
}
}
]
}
}
Since I am making this call from a python backend, I'm not sure if OAuth 2 as suggested in the message would be wise and applicable choice.
The model is already deployed and endpointed test on vertex ai and it worked fine. What I am trying to do is send same prediction task via an http post request using postman and this is what failed.
The request url looks like this:
https://[LOCATION]-aiplatform.googleapis.com/v1/projects/[PROJECT ID]/locations/[LOCATION]/endpoints/[ENDPOINT ID]:predict
Where token bearer is set in the potman authorization tab and instance set in request body.
I usually call Vertex AI endpoint this way:
from google.cloud import aiplatform
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="/home/user/1a2b3c4d.json"
aip_endpoint_name = (
f"projects/your-project/locations/us-west1/endpoints/1234567"
)
endpoint = aiplatform.Endpoint(aip_endpoint_name)
Here, you encode the input according to your needs.
def encode_64(input):
message = input
message_bytes = message.encode('ascii')
base64_bytes = base64.b64encode(message_bytes)
base64_message = base64_bytes.decode('ascii')
return base64_message
Check the model signature for the input type:
saved_model_cli show --dir /home/jupyter/model --all
Then call the endpoint
instances_list = [{"input_1": {"b64": encode_64("doctor")}}]
instances = [json_format.ParseDict(s, Value()) for s in instances_list]
results = endpoint.predict(instances=instances)
print(results.predictions)
According to the type of the input you are submitting to Vertex AI endpoint (integer, array, string, image), you may have to change the encoding.
If you don't want to use oAuth 2.0 for authentication when making http post request, you may want to use Application Default Credentials. In this documentation you can follow the step by step on getting service account key to passing credentials via environment variable.
Can you try following:
import subprocess
import base64
import requests
import json
def get_headers():
gcloud_access_token = subprocess.check_output("gcloud auth print-access-token".split(' ')).decode().rstrip('\n')
return {"authorization": "Bearer " + gcloud_access_token}
def send_get_request(uri):
return requests.get(uri, headers=get_headers(), verify=False)
It should work ff you have gcloud configured (you can do this by using gcloud init) on the machine you are sending request from and have permissions to access Vertex Prediction.
The code I've written seems to be what I need, however it doesn't work and I get a 401 error (authentication) I've tried everything: 1. Service account permissions 2. create secret id and key (not sure how to use those to get access token though) 3. Basically, tried everything for the past 2 days.
import requests
from google.oauth2 import service_account
METADATA_URL = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
SERVICE_ACCOUNT = [NAME-OF-SERVICE-ACCOUNT-USED-WITH-CLOUD-FUNCTION-WHICH-HAS-COMPUTE-ADMIN-PRIVILEGES]
def get_access_token():
url = '{}instance/service-accounts/{}/token'.format(
METADATA_URL, SERVICE_ACCOUNT)
# Request an access token from the metadata server.
r = requests.get(url, headers=METADATA_HEADERS)
r.raise_for_status()
# Extract the access token from the response.
access_token = r.json()['access_token']
return access_token
def start_vms(request):
request_json = request.get_json(silent=True)
request_args = request.args
if request_json and 'number_of_instances_to_create' in request_json:
number_of_instances_to_create = request_json['number_of_instances_to_create']
elif request_args and 'number_of_instances_to_create' in request_args:
number_of_instances_to_create = request_args['number_of_instances_to_create']
else:
number_of_instances_to_create = 0
access_token = get_access_token()
address = "https://www.googleapis.com/compute/v1/projects/[MY-PROJECT]/zones/europe-west2-b/instances?sourceInstanceTemplate=https://www.googleapis.com/compute/v1/projects/[MY-PROJECT]/global/instanceTemplates/[MY-INSTANCE-TEMPLATE]"
headers = {'token': '{}'.format(access_token)}
for i in range(1,number_of_instances_to_create):
data = {'name': 'my-instance-{}'.format(i)}
r = requests.post(address, data=data, headers=headers)
r.raise_for_status()
print("my-instance-{} created".format(i))
Any advice/guidance? If someone could tell me how to get an access token using secret Id and key. Also, I'm not too sure if OAuth2.0 will work because I essentially want to turn these machines on, and they do some processing and then self destruct. So there is no user involvement to allow access. If OAuth2.0 is the wrong way to go about it, what else can I use?
I tried using gcloud, but subprocess'ing gcloud commands aren't recommended.
I did something similar to this, though I used the Node 10 Firebase Functions runtime, but should be very similar never-the-less.
I agree that OAuth is not the correct solution since there is no user involved.
What you need to use is 'Application Default Credentials' which is based on the permissions available to your cloud functions' default service account which will be the one labelled as "App Engine default service account" here:
https://console.cloud.google.com/iam-admin/serviceaccounts?folder=&organizationId=&project=[YOUR_PROJECT_ID]
(For my project that service account already had the permissions necessary for starting and stopping GCE instances, but for other API's I have grant it permissions manually.)
ADC is for server-to-server API calls. To use it I called google.auth.getClient (of the Google APIs Auth Library) with just the scope, ie. "https://www.googleapis.com/auth/cloud-platform".
This API is very versatile in that it returns whatever credentials you need, so when I am running on cloud functions it returns a 'Compute' object and when I'm running in the emulator it gives me a "UserRefreshClient" object.
I then include that auth object in my call to compute.instances.insert() and compute.instances.stop().
Here the template I used for testing my code...
{
name: 'base',
description: 'Temporary instance used for testing.',
tags: { items: [ 'test' ] },
machineType: `zones/${zone}/machineTypes/n1-standard-1`,
disks: [
{
autoDelete: true, // you will want this!
boot: true,
type: 'PERSISTENT',
initializeParams: {
diskSizeGb: '10',
sourceImage: "projects/ubuntu-os-cloud/global/images/ubuntu-minimal-1804-bionic-v20190628",
}
}
],
networkInterfaces: [
{
network: `https://www.googleapis.com/compute/v1/projects/${projectId}/global/networks/default`,
accessConfigs: [
{
name: 'External NAT',
type: 'ONE_TO_ONE_NAT'
}
]
}
],
}
Hope that helps.
If you’re getting a 401 error that means that the access token you're using is either expired or invalid.
This guide will be able to show you how to request OAuth 2.0 access tokens and make API calls using a Service Account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
The .json file mentioned is the private key you create in IAM & Admin under your service account.
Receiving the following error response when doing a basic Graph API POST using REQUESTS in Python:
{
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"request-id": "36c01b2f-5c5c-438a-bd10-b3ebbc1a17c9",
"date": "2019-04-05T22:39:37"
}
}
}
Here is my token request and Graph request using REQUESTS in Python:
redirect_uri = "https://smartusys.sharepoint.com"
client_id = 'd259015e-****-4e99-****-aaad67057124'
client_secret = '********'
tennant_id = '15792366-ddf0-****-97cb-****'
scope = 'https://graph.microsoft.com/.default'
####GET A TOKEN
payload = "client_id="+client_id+"&scope="+scope+"&client_secret="+client_secret+"&grant_type=client_credentials"
headers = {'content-type':'application/x-www-form-urlencoded'}
tokenResponse = requests.post('https://login.microsoftonline.com/'+tennant_id+'/oauth2/v2.0/token',headers=headers, data=payload)
json_tokenObject = json.loads(tokenResponse.text)
authToken = json_tokenObject['access_token']
#### Make a call to the graph API
graphResponse = requests.get('https://graph.microsoft.com/v1.0/me/',headers={'Authorization':'Bearer '+authToken})
if tokenResponse.status_code != 200:
print('Error code: ' +graphResponse.status_code)
print(graphResponse.text)
exit()
print('Request successfull: Response: ')
print(graphResponse.text)
print('Press any key to continue...')
x=input()
According to the documentation ( https://learn.microsoft.com/en-us/graph/api/resources/users?view=graph-rest-1.0 ) for this /me call, I need just one of the following permissions:
User.ReadBasic.All
User.Read
User.ReadWrite
User.Read.All
User.ReadWrite.All
Directory.Read.All
Directory.ReadWrite.All
Directory.AccessAsUser.All
and I have all of these on both application and delegated permissions in the azure application manager.
What am I doing wrong here? I feel like it's something small but I just can't figure this out.
I decoded my token using: http://calebb.net/ and I do not see a spot for "AUD" or "role" or "scope" so maybe that is where I am doing it wrong?
I looked everywhere and can't find a resolution, any help would be VERY much appreciated.
Thank you.
This sounds like you forgot to "Grant Permissions" to your application.
See this answer.
I finally figured this out, it had to do with Admin rights that needed to be granted by the Admin for our Office 365.
it was as simple as giving my Office admin the following link and having him approve it:
https://login.microsoftonline.com/{TENNANT ID HERE}/adminconsent?client_id={CLIENT ID HERE}
Instantly worked.
I'm trying to get a specific user OAuth2 bearer token using HTTP POST request, and nothing seems to work.
login_url = 'https://login.microsoftonline.com/'
authorize_endpoint = '{0}{1}{2}'.format(login_url,config.tenant_id,'/oauth2/authorize')
bodyvals = {'client_id': config.client_id,
'client_secret': config.client_secret,
'grant_type': 'client_credentials',
'resource':config.resource_endpoint}
return requests.post(authorize_endpoint, data=bodyvals)
The above code works, but generates a token on behalf of the application.
I can't seem to find a way to pass in the users credentials, and no documentation on this whatsoever.
Generally I don't care if the answer is in Python or Powershell or just a general explanation, I just don't seem to understand how to properly do that with AAD.
You can do it manually, see my other answer here: https://stackoverflow.com/a/40844983/1658906.
You must use grant_type=password and call the oauth2/token endpoint. Here is the C# version for authenticating:
private async Task<string> GetAccessToken()
{
string tokenEndpointUri = Authority + "oauth2/token";
var content = new FormUrlEncodedContent(new []
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", Username),
new KeyValuePair<string, string>("password", Password),
new KeyValuePair<string, string>("client_id", ClientId),
new KeyValuePair<string, string>("client_secret", ClientSecret),
new KeyValuePair<string, string>("resource", PowerBiResourceUri)
}
);
using (var client = new HttpClient())
{
HttpResponseMessage res = await client.PostAsync(tokenEndpointUri, content);
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
return tokenRes.AccessToken;
}
}
In the request you must specify:
Username
Password
Client ID
Client secret
The resource URI
For GraphAPI, resource is "https://graph.windows.net/"
If you don't want to use ADAL, you might however take a look at the code for usage of "resource". This scenario is covered, so consider ADAL as a big sample :)
Also, msrestazure has a UserPassCredentials instance that works too on GraphAPI.