1+ import requests
2+ from datetime import datetime , timedelta
3+ from urllib .parse import quote
4+ from .base import BaseClient
5+
6+ BASE_URL = 'https://developer.api.autodesk.com/authentication/v1'
7+
8+ class AuthenticationClient (BaseClient ):
9+ """
10+ Forge Authentication service client.
11+
12+ For more details, see https://forge.autodesk.com/en/docs/oauth/v2/reference/http.
13+ """
14+
15+ def __init__ (self , base_url = BASE_URL ):
16+ BaseClient .__init__ (self , base_url )
17+
18+ def authenticate (self , client_id , client_secret , scopes ):
19+ """Get a two-legged access token by providing your app’s client ID and secret.
20+
21+ Parameters:
22+ client_id (string): Client ID of the app.
23+ client_secret (string): Client secret of the app.
24+ scopes (list): List of required scopes.
25+
26+ Returns:
27+ object: Parsed response object with properties 'token_type', 'access_token', and 'expires_in'.
28+ """
29+ form = {
30+ 'client_id' : client_id ,
31+ 'client_secret' : client_secret ,
32+ 'grant_type' : 'client_credentials' ,
33+ 'scope' : ' ' .join (scopes )
34+ }
35+ return self ._post ('/authenticate' , form = form ).json ()
36+
37+ def get_authorization_url (self , client_id , response_type , redirect_uri , scopes , state = None ):
38+ """Generate a URL to redirect an end user to
39+ in order to acquire the user’s consent for your app to access the specified resources.
40+
41+ Parameters:
42+ client_id (string): Client ID of the app.
43+ response_type (string): Must be either 'code' for authorization code grant flow or 'token' for implicit grant flow.
44+ redirect_uri (string): URL-encoded callback URL that the end user will be redirected to after completing the authorization flow.
45+ scopes (list): List of required scopes.
46+ state (string): Optional payload containing arbitrary data that the authentication flow will pass back verbatim in a state query parameter to the callback URL.
47+
48+ Returns:
49+ string: Complete authorization URL.
50+ """
51+ url = 'https://developer.api.autodesk.com/authentication/v1/authorize'
52+ url = url + '?client_id={}' .format (quote (client_id ))
53+ url = url + '&response_type={}' .format (response_type )
54+ url = url + '&redirect_uri={}' .format (quote (redirect_uri ))
55+ url = url + '&scope={}' .format (quote (' ' .join (scopes )))
56+ if state :
57+ url += '&state={}' .format (quote (state ))
58+ return url
59+
60+ def get_token (self , client_id , client_secret , code , redirect_uri ):
61+ """Exchange an authorization code extracted from a 'GET authorize' callback
62+ for a three-legged access token. This API will only be used when the 'Authorization Code' grant type
63+ is being adopted.
64+
65+ Parameters:
66+ client_id (string): Client ID of the app.
67+ client_secret (string): Client secret of the app.
68+ code (string): The authorization code captured from the code query parameter when the 'GET authorize' redirected back to the callback URL.
69+ redirect_uri (string): Must match the redirect_uri parameter used in 'GET authorize'.
70+
71+ Returns:
72+ object: Parsed response object with properties 'token_type', 'access_token', 'refresh_token', and 'expires_in'.
73+ """
74+ form = {
75+ 'client_id' : client_id ,
76+ 'client_secret' : client_secret ,
77+ 'grant_type' : 'authorization_code' ,
78+ 'code' : code ,
79+ 'redirect_uri' : redirect_uri
80+ }
81+ return self ._post ('/gettoken' , form = form ).json ()
82+
83+ def refresh_token (self , client_id , client_secret , refresh_token , scopes ):
84+ """Acquire a new access token by using the refresh token provided by the `POST gettoken` endpoint.
85+
86+ Parameters:
87+ client_id (string): Client ID of the app.
88+ client_secret (string): Client secret of the app.
89+ refresh_token (string): The refresh token used to acquire a new access token.
90+ scopes (list): List of required scopes.
91+
92+ Returns:
93+ object: Parsed response object with properties 'token_type', 'access_token', 'refresh_token', and 'expires_in'.
94+ """
95+ form = {
96+ 'client_id' : client_id ,
97+ 'client_secret' : client_secret ,
98+ 'grant_type' : 'refresh_token' ,
99+ 'refresh_token' : refresh_token ,
100+ 'scope' : ' ' .join (scopes )
101+ }
102+ return self ._post ('/refreshtoken' , form = form ).json ()
103+
104+ def get_profile (self , access_token ):
105+ """Get the profile information of an authorizing end user in a three-legged context.
106+
107+ Parameters:
108+ access_token (string): Token obtained via a three-legged OAuth flow.
109+
110+ Returns:
111+ object: Parsed response object with properties 'userId', 'userName', 'emaillId', 'firstName', 'lastName', etc.
112+ """
113+ headers = {
114+ 'Authorization' : 'Bearer {}' .format (access_token )
115+ }
116+ return self ._get ('/users/@me' , headers = headers ).json ()
117+
118+ class PassiveTokenProvider :
119+ def __init__ (self , token ):
120+ self .token = token
121+ def get_token (self , scopes ):
122+ return self .token
123+
124+ class ActiveTokenProvider :
125+ def __init__ (self , client_id , client_secret ):
126+ self .client_id = client_id
127+ self .client_secret = client_secret
128+ self .auth_client = AuthenticationClient ()
129+ self .cache = {}
130+ def get_token (self , scopes ):
131+ cache_key = '+' .join (scopes )
132+ now = datetime .now ()
133+ if cache_key in self .cache :
134+ auth = self .cache [cache_key ]
135+ if auth ['expires_at' ] > now :
136+ return auth
137+ auth = self .auth_client .authenticate (self .client_id , self .client_secret , scopes )
138+ auth ['expires_at' ] = now + timedelta (0 , auth ['expires_in' ])
139+ return auth
140+
141+ class BaseOAuthClient (BaseClient ):
142+ def __init__ (self , token_provider , base_url ):
143+ BaseClient .__init__ (self , base_url )
144+ self .token_provider = token_provider
145+
146+ def _get (self , url , scopes , params = None , headers = None ):
147+ if not headers :
148+ headers = {}
149+ self ._set_auth_headers (headers , scopes )
150+ return BaseClient ._get (self , url , params , headers )
151+
152+ def _post (self , url , scopes , form = None , json = None , buff = None , params = None , headers = None ):
153+ if not headers :
154+ headers = {}
155+ self ._set_auth_headers (headers , scopes )
156+ return BaseClient ._post (self , url , form , json , buff , params , headers )
157+
158+ def _put (self , url , scopes , form = None , json = None , buff = None , params = None , headers = None ):
159+ if not headers :
160+ headers = {}
161+ self ._set_auth_headers (headers , scopes )
162+ return BaseClient ._put (self , url , form , json , buff , params , headers )
163+
164+ def _delete (self , url , scopes , params = None , headers = None ):
165+ if not headers :
166+ headers = {}
167+ self ._set_auth_headers (headers , scopes )
168+ return BaseClient ._delete (self , url , params , headers )
169+
170+ def _set_auth_headers (self , headers , scopes ):
171+ if not 'Authorization' in headers :
172+ auth = self .token_provider .get_token (scopes )
173+ headers ['Authorization' ] = 'Bearer {}' .format (auth ['access_token' ])
0 commit comments