Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
85bdb5e
Merge pull request #46 from AzureAD/release-0.4.0
rayluo May 22, 2019
67b3d60
Updating authority URL regexp to have escaped backslash
marstr May 23, 2019
ce450a7
Removing backslash from pattern
marstr May 24, 2019
3e1b3d7
Merge pull request #48 from marstr/issues/47
rayluo May 24, 2019
320d143
Avoid hardcoding secret, not even a placeholder
rayluo May 24, 2019
319a4bf
Concludes our Log Injection investigation
rayluo May 31, 2019
425dde8
Older apps on macOS may crash on NSNull from JSON
rayluo May 31, 2019
8935521
Merge pull request #50 from AzureAD/do-not-write-null-in-family-id
rayluo Jun 1, 2019
b56a384
Also updating inline comments for the secret param
rayluo Jun 3, 2019
bdd3803
Add id token decoder, which will also validate it
rayluo Jun 4, 2019
45bcb94
Merge remote-tracking branch into oauth2
rayluo Jun 4, 2019
6519ff2
Merge branch 'oauth2' into id-token-decoder
rayluo Jun 4, 2019
3afa44a
Add more test behavior to validate id token behaviors
rayluo Jun 4, 2019
d46e5b8
Merge branch 'id-token-decoder' into dev
rayluo Jun 4, 2019
8a7495a
Refactor cache to consolidate the add behavior
rayluo Jun 4, 2019
95b59f7
Merge branch 'token-cache-add-refactor' into dev
rayluo Jun 4, 2019
0b1e902
Merge pull request #49 from AzureAD/remove-secret
rayluo Jun 4, 2019
e931d02
ensure addends are ints
chlowell Jun 6, 2019
0075528
str.join raises when passed None
chlowell Jun 6, 2019
c0ea957
Explain the reason of this subtle change
rayluo Jun 11, 2019
4c52458
Merge pull request #55 from chlowell/imds-fixes
rayluo Jun 11, 2019
1e09105
Support sovereign cloud.
rayluo Jun 17, 2019
cb023ef
Merge pull request #62 from AzureAD/sovereign-support
rayluo Jun 17, 2019
4690131
MSAL Python 0.4.1
rayluo Jun 17, 2019
2791b62
* Pass through verify, timeout and proxies flag
May 20, 2019
f49436f
Shorten a line to within 80 chars
rayluo Jun 18, 2019
d79cc5a
Merge pull request #61 from roederja2/pass_through_verify
rayluo Jun 18, 2019
002a135
Change an optional scopes parameter to be required
rayluo Jun 18, 2019
54dc244
Merge pull request #64 from AzureAD/scopes-are-required-in-username-p…
rayluo Jun 18, 2019
74df5ee
Merge pull request #65 from AzureAD/dev
rayluo Jun 18, 2019
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
7 changes: 4 additions & 3 deletions msal/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


# The __init__.py will import this. Not the other way around.
__version__ = "0.4.0"
__version__ = "0.4.1"

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -271,7 +271,8 @@ def _get_authority_aliases(self, instance):
if not self.authority_groups:
resp = requests.get(
"https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://login.microsoftonline.com/common/oauth2/authorize",
headers={'Accept': 'application/json'})
headers={'Accept': 'application/json'},
verify=self.verify, proxies=self.proxies, timeout=self.timeout)
resp.raise_for_status()
self.authority_groups = [
set(group['aliases']) for group in resp.json()['metadata']]
Expand Down Expand Up @@ -511,7 +512,7 @@ def acquire_token_by_device_flow(self, flow, **kwargs):
**kwargs)

def acquire_token_by_username_password(
self, username, password, scopes=None, **kwargs):
self, username, password, scopes, **kwargs):
"""Gets a token for a given resource via user credentails.

See this page for constraints of Username Password Flow.
Expand Down
13 changes: 10 additions & 3 deletions msal/authority.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import re
import logging

import requests

from .exceptions import MsalServiceError


logger = logging.getLogger(__name__)
WORLD_WIDE = 'login.microsoftonline.com' # There was an alias login.windows.net
WELL_KNOWN_AUTHORITY_HOSTS = set([
WORLD_WIDE,
Expand Down Expand Up @@ -38,14 +40,15 @@ def __init__(self, authority_url, validate_authority=True,
canonicalized, self.instance, tenant = canonicalize(authority_url)
tenant_discovery_endpoint = ( # Hard code a V2 pattern as default value
'https://{}/{}/v2.0/.well-known/openid-configuration'
.format(WORLD_WIDE, tenant))
.format(self.instance, tenant))
if validate_authority and self.instance not in WELL_KNOWN_AUTHORITY_HOSTS:
tenant_discovery_endpoint = instance_discovery(
canonicalized + "/oauth2/v2.0/authorize",
verify=verify, proxies=proxies, timeout=timeout)
openid_config = tenant_discovery(
tenant_discovery_endpoint,
verify=verify, proxies=proxies, timeout=timeout)
logger.debug("openid_config = %s", openid_config)
self.authorization_endpoint = openid_config['authorization_endpoint']
self.token_endpoint = openid_config['token_endpoint']
_, _, self.tenant = canonicalize(self.token_endpoint) # Usually a GUID
Expand All @@ -65,7 +68,7 @@ def user_realm_discovery(self, username):

def canonicalize(url):
# Returns (canonicalized_url, netloc, tenant). Raises ValueError on errors.
match_object = re.match("https://([^/]+)/([^/\?#]+)", url.lower())
match_object = re.match(r'https://([^/]+)/([^/?#]+)', url.lower())
if not match_object:
raise ValueError(
"Your given address (%s) should consist of "
Expand All @@ -76,7 +79,11 @@ def canonicalize(url):
def instance_discovery(url, response=None, **kwargs):
# Returns tenant discovery endpoint
resp = requests.get( # Note: This URL seemingly returns V1 endpoint only
'https://{}/common/discovery/instance'.format(WORLD_WIDE),
'https://{}/common/discovery/instance'.format(
WORLD_WIDE # Historically using WORLD_WIDE. Could use self.instance too
# See https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.0.0/src/Microsoft.Identity.Client/Instance/AadInstanceDiscovery.cs#L101-L103
# and https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/4.0.0/src/Microsoft.Identity.Client/Instance/AadAuthority.cs#L19-L33
),
params={'authorization_endpoint': url, 'api-version': '1.0'},
**kwargs)
payload = response or resp.json()
Expand Down
4 changes: 2 additions & 2 deletions msal/oauth2cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__version__ = "0.1.0"
__version__ = "0.2.0"

from .oauth2 import Client
from .oidc import Client
from .assertion import JwtSigner

70 changes: 70 additions & 0 deletions msal/oauth2cli/oidc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import json
import base64
import time

from . import oauth2


def base64decode(raw):
"""A helper can handle a padding-less raw input"""
raw += '=' * (-len(raw) % 4) # https://stackoverflow.com/a/32517907/728675
return base64.b64decode(raw).decode("utf-8")


def decode_id_token(id_token, client_id=None, issuer=None, nonce=None, now=None):
"""Decodes and validates an id_token and returns its claims as a dictionary.

ID token claims would at least contain: "iss", "sub", "aud", "exp", "iat",
per `specs <https://openid.net/specs/openid-connect-core-1_0.html#IDToken>`_
and it may contain other optional content such as "preferred_username",
`maybe more <https://openid.net/specs/openid-connect-core-1_0.html#Claims>`_
"""
decoded = json.loads(base64decode(id_token.split('.')[1]))
err = None # https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
if issuer and issuer != decoded["iss"]:
# https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
err = ('2. The Issuer Identifier for the OpenID Provider, "%s", '
"(which is typically obtained during Discovery), "
"MUST exactly match the value of the iss (issuer) Claim.") % issuer
if client_id:
valid_aud = client_id in decoded["aud"] if isinstance(
decoded["aud"], list) else client_id == decoded["aud"]
if not valid_aud:
err = "3. The aud (audience) Claim must contain this client's client_id."
# Per specs:
# 6. If the ID Token is received via direct communication between
# the Client and the Token Endpoint (which it is in this flow),
# the TLS server validation MAY be used to validate the issuer
# in place of checking the token signature.
if (now or time.time()) > decoded["exp"]:
err = "9. The current time MUST be before the time represented by the exp Claim."
if nonce and nonce != decoded.get("nonce"):
err = ("11. Nonce must be the same value "
"as the one that was sent in the Authentication Request")
if err:
raise RuntimeError("%s id_token was: %s" % (
err, json.dumps(decoded, indent=2)))
return decoded


class Client(oauth2.Client):
"""OpenID Connect is a layer on top of the OAuth2.

See its specs at https://openid.net/connect/
"""

def decode_id_token(self, id_token, nonce=None):
"""See :func:`~decode_id_token`."""
return decode_id_token(
id_token, nonce=nonce,
client_id=self.client_id, issuer=self.configuration.get("issuer"))

def _obtain_token(self, grant_type, *args, **kwargs):
"""The result will also contain one more key "id_token_claims",
whose value will be a dictionary returned by :func:`~decode_id_token`.
"""
ret = super(Client, self)._obtain_token(grant_type, *args, **kwargs)
if "id_token" in ret:
ret["id_token_claims"] = self.decode_id_token(ret["id_token"])
return ret

Loading