Skip to content

Client credential certificate #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions msal/application.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from . import request
from .client_credential import ClientCredentialRequest


class ClientApplication(object):
Expand Down Expand Up @@ -35,7 +35,7 @@ def __init__(self, client_id, client_credential, user_token_cache, **kwargs):
self.app_token_cache = None # TODO

def acquire_token_for_client(self, scope, policy=''):
return request.ClientCredentialRequest(
return ClientCredentialRequest(
client_id=self.client_id, client_credential=self.client_credential,
scope=scope, policy=policy, authority=self.authority).run()

56 changes: 56 additions & 0 deletions msal/client_credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import time
import binascii
import base64
import uuid

import jwt

from .oauth2 import ClientCredentialGrant
from .request import BaseRequest


class ClientCredentialRequest(BaseRequest):
def __init__(self, **kwargs):
super(ClientCredentialRequest, self).__init__(**kwargs)
self.grant = ClientCredentialGrant(
self.client_id, token_endpoint=self.token_endpoint)

def get_token(self):
if isinstance(self.client_credential, dict):
return self.get_token_by_certificate(
self.client_credential['certificate'],
self.client_credential['thumbprint'])
else:
return self.get_token_by_secret(self.client_credential)

def get_token_by_secret(self, secret):
return self.grant.get_token(scope=self.scope, client_secret=secret)

def get_token_by_certificate(self, pem, thumbprint):
JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
assertion = create_jwt_assertion(
pem, thumbprint, self.grant.token_endpoint, self.client_id)
return self.grant.get_token(
client_assertion_type=JWT_BEARER, client_assertion=assertion,
scope=self.scope)


def create_jwt_assertion(
private_pem, thumbprint, audience, issuer,
subject=None, # If None is specified, the value of issuer will be used
not_valid_before=None, # If None, the current time will be used
jwt_id=None): # If None is specified, a UUID will be generated
assert '-----BEGIN PRIVATE KEY-----' in private_pem, "Need a standard PEM"
nbf = time.time() if not_valid_before is None else not_valid_before
payload = { # key names are all from JWT standard names
'aud': audience,
'iss': issuer,
'sub': subject or issuer,
'nbf': nbf,
'exp': nbf + 10*60, # 10 minutes
'jti': str(uuid.uuid4()) if jwt_id is None else jwt_id,
}
# Per http://self-issued.info/docs/draft-jones-json-web-token-01.html
h = {'x5t': base64.urlsafe_b64encode(binascii.a2b_hex(thumbprint)).decode()}
return jwt.encode(payload, private_pem, algorithm='RS256', headers=h)

26 changes: 14 additions & 12 deletions msal/request.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import time

from . import oauth2
from .exceptions import MsalServiceError


class BaseRequest(object):
TOKEN_ENDPOINT_PATH = 'oauth2/v2.0/token'

def __init__(
self, authority=None, token_cache=None, scope=None, policy="",
self, authority=None, token_cache=None,
scope=None, policy="", # TBD: If scope and policy are paramters
# of both high level ClientApplication.acquire_token()
# and low level oauth2.*Grant.get_token(),
# shouldn't they be the parameters for run()?
client_id=None, client_credential=None, authenticator=None,
support_adfs=False, restrict_to_single_user=False):
if not scope:
raise ValueError("scope cannot be empty")
self.__dict__.update(locals())

# TODO: Temporary solution here
self.token_endpoint = authority
if authority.startswith('https://login.microsoftonline.com/common/'):
self.token_endpoint += 'oauth2/v2.0/token'
elif authority.startswith('https://login.windows.net/'): # AAD?
self.token_endpoint += 'oauth2/token'
if policy:
self.token_endpoint += '?policy={}'.format(policy)

def run(self):
"""Returns a dictionary, which typically contains following keys:

Expand Down Expand Up @@ -55,12 +66,3 @@ def __timestamp(self, seconds_from_now=None): # Returns timestamp IN SECOND
def get_token(self):
raise NotImplemented("Use proper sub-class instead")


class ClientCredentialRequest(BaseRequest):
def get_token(self):
return oauth2.ClientCredentialGrant(
self.client_id,
token_endpoint="%s%s?policy=%s" % (
self.authority, self.TOKEN_ENDPOINT_PATH, self.policy),
).get_token(scope=self.scope, client_secret=self.client_credential)

5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
requests>=2,<3

PyJWT>=1,<2
#1.1.0 is the first that can be installed on windows
cryptography>=1.1,<2