-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from gremlin/oauth_completion
Oauth completion
- Loading branch information
Showing
6 changed files
with
144 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
[bumpversion] | ||
current_version = 0.14.3 | ||
current_version = 0.15.1 | ||
commit = True | ||
tag = True | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,33 +116,31 @@ config.team_id = team_id | |
|
||
#### Authentication with OAUTH | ||
|
||
***Experimental - not fully implemented*** | ||
|
||
To authentication through a desired OAUTH workflow, the required information is similar to `gremlinapi.login()`. | ||
|
||
When successfully authenticated through OAUTH, the bearer token, used later in the API workflow, is returned. | ||
|
||
```python | ||
from gremlinapi.oauth import GremlinAPIOAUTH | ||
|
||
GREMLIN_COMPANY = "Hooli" | ||
GREMLIN_USER = "[email protected]" | ||
GREMLIN_PASSWORD = "y0urPa$$w0rd" | ||
GREMLIN_COMPANY = "Your Company Name" | ||
USERNAME = "[email protected]" | ||
PASSWORD = "y0urPa$$w0rd" | ||
OAUTH_LOGIN = "http://your.oauth.provider/login" | ||
|
||
auth_args = { | ||
"email":GREMLIN_USER, | ||
"password": GREMLIN_PASSWORD, | ||
"clientId": "mocklab_oauth2", | ||
"companyName": GREMLIN_COMPANY, | ||
"email":USERNAME, | ||
"password": PASSWORD, | ||
"client_id": "mocklab_oauth2", | ||
"company_name": GREMLIN_COMPANY, | ||
"oauth_login_uri": OAUTH_LOGIN, | ||
} | ||
|
||
bearer_token = GremlinAPIOAUTH.authenticate(**auth_args) | ||
``` | ||
|
||
#### OAUTH Configuration | ||
|
||
***Experimental - not fully implemented*** | ||
|
||
OAUTH can be configured through an API endpoint per the following configuration dictionary and code example. | ||
|
||
You must previous be logged in or otherwise authenticated for the below code to succeed. | ||
|
@@ -154,15 +152,15 @@ GREMLIN_TEAM_ID = "your-team-id" | |
|
||
config_body = { | ||
# Used to authenticate against the OAuth provider. We will redirect the user to this URL when they initate a OAuth login. | ||
"authorizationUri": "your-authorization-uri", | ||
"authorizationUri": "http://your.oauth.provider/authorize", | ||
# Used to exchange an OAuth code, obtained after logging into the OAuth provider, for an access token. | ||
"tokenUri": "your-token-uri", | ||
"tokenUri": "http://your.oauth.provider/oauth/token", | ||
# Used to query for the email of the user.. | ||
"userInfoUri": "your-userinfo-uri", | ||
"userInfoUri": "http://your.oauth.provider/userinfo", | ||
# The public identifier obtained when registering Gremlin with your OAuth provider. | ||
"clientId": "mocklab_oauth2", | ||
"clientId": "your_client_id", | ||
# The secret obtained when registering Gremlin with your OAuth provider. | ||
"clientSecret": "foo", | ||
"clientSecret": "your_client_secret", | ||
# Define what level of access the access token will have that Gremlin obtains during the OAuth login. The default is `email`. If you change it from the default, the scope provided <strong>must</strong> be able to read the email of the user. | ||
"scope":"email", | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Copyright (C) 2020 Kyle Bouchard <[email protected]>, Gremlin Inc <[email protected]> | ||
# Copyright (C) 2021 Kyle Bouchard <[email protected]>, Gremlin Inc <[email protected]> | ||
|
||
import logging | ||
import json | ||
|
@@ -26,13 +26,18 @@ | |
|
||
from typing import Union, Type, Any, Tuple | ||
|
||
from gremlinapi.util import ( | ||
GREMLIN_OAUTH_LOGIN, | ||
GREMLIN_OAUTH_COMPANIES_URI, | ||
GREMLIN_SSO_USER_AUTH, | ||
GREMLIN_OAUTH_CALLBACK, | ||
) | ||
|
||
log = logging.getLogger("GremlinAPI.client") | ||
|
||
|
||
class GremlinAPIOAUTH(GremlinAPI): | ||
@classmethod | ||
@experimental("`configure` method not fully implemented") | ||
def configure( | ||
cls, | ||
company_id: str = "", | ||
|
@@ -53,16 +58,16 @@ def configure( | |
`kwargs` is required in the following format: | ||
{ | ||
companyId : Defines the Company ID for OAUTH | ||
authorizationUri : Used to authenticate against the OAuth provider. | ||
'companyId' : Defines the Company ID for OAUTH | ||
'authorizationUri' : Used to authenticate against the OAuth provider. | ||
We will redirect the user to this URL when they initate a OAuth login. | ||
tokenUri : Used to exchange an OAuth code. | ||
'tokenUri' : Used to exchange an OAuth code. | ||
This is obtained after logging into the OAuth provider, for an access token. | ||
userInfoUri : Used to query for the email of the user. | ||
clientId : The public identifier obtained when registering Gremlin with your OAuth | ||
'userInfoUri' : Used to query for the email of the user. | ||
'clientId' : The public identifier obtained when registering Gremlin with your OAuth | ||
provider. | ||
clientSecret : The secret obtained when registering Gremlin with your OAuth provider. | ||
scope : (OPTIONAL) Define what level of access the access token will have that Gremlin | ||
'clientSecret' : The secret obtained when registering Gremlin with your OAuth provider. | ||
'scope' : (OPTIONAL) Define what level of access the access token will have that Gremlin | ||
obtains during the OAuth login. The default is `email`. If you change it from the | ||
default, the scope provided must be able to read the email of the user. | ||
} | ||
|
@@ -78,9 +83,7 @@ def configure( | |
log.error(error_msg) | ||
raise GremlinParameterError(error_msg) | ||
|
||
endpoint: str = ( | ||
f"https://api.gremlin.com/v1/companies/{company_id}/oauth/settings" | ||
) | ||
endpoint: str = f"{GREMLIN_OAUTH_COMPANIES_URI}/{company_id}/oauth/settings" | ||
data: dict = { | ||
"authorizationUri": cls._error_if_not_param("authorizationUri", **kwargs), | ||
"tokenUri": cls._error_if_not_param("tokenUri", **kwargs), | ||
|
@@ -94,7 +97,6 @@ def configure( | |
return resp.status_code | ||
|
||
@classmethod | ||
@experimental | ||
def initiate_oauth( | ||
cls, | ||
company_name: str, | ||
|
@@ -113,12 +115,9 @@ def initiate_oauth( | |
state_cookie, oauth_provider_login_url : Tuple[str, str] | ||
""" | ||
endpoint: str = ( | ||
f"https://api.gremlin.com/v1/oauth/login?companyName={company_name}" | ||
) | ||
endpoint: str = f"{GREMLIN_OAUTH_LOGIN}?companyName={company_name}" | ||
|
||
# Initiates OAUTH login with Gremlin | ||
# `allow_redirects` = False enables capture of the response to extract the state cookie | ||
# `status_code` 307 is a redirect to the OAUTH provider | ||
payload: dict = cls._payload(**{"headers": https_client.header()}) | ||
(resp, body) = https_client.api_call("GET", endpoint, **payload) | ||
|
@@ -132,7 +131,6 @@ def initiate_oauth( | |
return state_cookie, oauth_provider_login_url | ||
|
||
@classmethod | ||
@experimental | ||
def get_callback_url( | ||
cls, | ||
oauth_provider_login_url: str, | ||
|
@@ -151,37 +149,32 @@ def get_callback_url( | |
`data` is in the following format: | ||
{ | ||
email: Login email for your user, | ||
password: Login password for your user, | ||
state: Value of the state cookie obtained in the previous step, | ||
redirectUri: URL where your provider should redirect you to after authenticating. | ||
'email': Login email for your user, | ||
'password': Login password for your user, | ||
'state': Value of the state cookie obtained in the previous step, | ||
'redirectUri': URL where your provider should redirect you to after authenticating. | ||
It should be https://api.gremlin.com/v1/oauth/callback, | ||
clientId: Client Id obtained when registering Gremlin with your OAuth provider, | ||
'clientId': Client Id obtained when registering Gremlin with your OAuth provider, | ||
} | ||
Returns | ||
------ | ||
gremlin_callback_url : str | ||
""" | ||
payload: dict = cls._payload(**{"headers": https_client.header(), "body": data}) | ||
payload: dict = cls._payload(**{"headers": https_client.header(), "data": data}) | ||
(resp, body) = https_client.api_call( | ||
"POST", oauth_provider_login_url, **payload | ||
) | ||
|
||
# You have now successfully authenticted with your OAuth provider, | ||
# now continue the flow by following the redirect your OAuth provider | ||
# created back to Gremlins /oauth/callback endpoint | ||
# log.info(str(resp.headers)) | ||
gremlin_callback_url = resp.headers["Location"] | ||
# gremlin_callback_url = auth_body["redirectUri"] | ||
assert gremlin_callback_url != None | ||
# NOTE TO SELF: the `location` key is nonexistant on the resp, and the | ||
# redirectUri is `null` in the original state cookie as returned from the mock endpoint | ||
return gremlin_callback_url | ||
|
||
@classmethod | ||
@experimental | ||
def get_access_token( | ||
cls, | ||
state_cookie: str, | ||
|
@@ -209,12 +202,9 @@ def get_access_token( | |
# redirect URL you are following and it needs to match the | ||
# value in the cookie. This helps prevent CSRF attacks. | ||
cookie = {"oauth_state": state_cookie} | ||
payload: dict = cls._payload( | ||
**{"headers": https_client.header(), "cookies": cookie} | ||
(resp, body) = https_client.api_call( | ||
"GET", gremlin_callback_url, **{"cookies": cookie} | ||
) | ||
(resp, body) = https_client.api_call("GET", gremlin_callback_url, **payload) | ||
|
||
# gremlin_callback_response = requests.get(gremlin_callback_url, cookies=cookie) | ||
# The response from the callback endpoint will contain the `access_token` in JSON | ||
# This is the end of the OAuth specific flow. This `access_token` can | ||
# now be exchanged for a Gremlin session. | ||
|
@@ -225,7 +215,6 @@ def get_access_token( | |
return access_token | ||
|
||
@classmethod | ||
@experimental | ||
def get_bearer_token( | ||
cls, | ||
company_name: str, | ||
|
@@ -252,8 +241,8 @@ def get_bearer_token( | |
"accessToken": access_token, | ||
"provider": "oauth", | ||
} | ||
payload: dict = cls._payload(**{"headers": https_client.header(), "body": body}) | ||
endpoint = f"https://api.gremlin.com/v1/users/auth/sso?getCompanySession=true" | ||
payload: dict = cls._payload(**{"data": body}) | ||
endpoint = f"{GREMLIN_SSO_USER_AUTH}?getCompanySession=true" | ||
(resp, body) = https_client.api_call("POST", endpoint, **payload) | ||
assert resp.status_code == 200 | ||
|
||
|
@@ -267,7 +256,6 @@ def get_bearer_token( | |
return bearer_token | ||
|
||
@classmethod | ||
@experimental | ||
def authenticate( | ||
cls, | ||
https_client: Type[GremlinAPIHttpClient] = get_gremlin_httpclient(), | ||
|
@@ -284,29 +272,33 @@ def authenticate( | |
`kwargs` is required in the following format: | ||
{ | ||
companyName : The company for which the oauth authentication should commence, | ||
email: Login email for your user, | ||
password: Login password for your user, | ||
clientId: Client Id obtained when registering Gremlin with your OAuth provider, | ||
'company_name' : The company for which the oauth authentication should commence, | ||
'email': Login email for your user, | ||
'password': Login password for your user, | ||
'client_id': Client Id obtained when registering Gremlin with your OAuth provider, | ||
'oauth_login_uri': The login Uri from your OAUTH provider, | ||
} | ||
Returns | ||
------ | ||
bearer_token : str | ||
""" | ||
company_name = cls._error_if_not_param("companyName", **kwargs) | ||
|
||
company_name = cls._error_if_not_param("company_name", **kwargs) | ||
(state_cookie, oauth_provider_login_url) = cls.initiate_oauth( | ||
company_name, https_client | ||
) | ||
auth_body = { | ||
"email": cls._error_if_not_param("email", **kwargs), | ||
"password": cls._error_if_not_param("password", **kwargs), | ||
"state": state_cookie, # obtained in earlier step | ||
"redirectUri": "https://api.gremlin.com/v1/oauth/callback", | ||
"clientId": cls._error_if_not_param("clientId", **kwargs), | ||
"redirectUri": GREMLIN_OAUTH_CALLBACK, | ||
"clientId": cls._error_if_not_param("client_id", **kwargs), | ||
} | ||
gremlin_callback_url = cls.get_callback_url(oauth_provider_login_url, auth_body) | ||
gremlin_callback_url = cls.get_callback_url( | ||
cls._error_if_not_param("oauth_login_uri", **kwargs), auth_body | ||
) | ||
access_token = cls.get_access_token(state_cookie, gremlin_callback_url) | ||
bearer_token = cls.get_bearer_token(company_name, access_token) | ||
return bearer_token |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.