Skip to content

Commit

Permalink
feat: add revokeToken and downscopeToken methods + make asUser
Browse files Browse the repository at this point in the history
…and `asEnterprise` return new auth object (#48)

* generated with codegen at box/box-codegen@7e31d1a and spec at box/box-openapi@e121116

* generated with codegen at box/box-codegen@ac286a8 and spec at box/box-openapi@6774708
  • Loading branch information
box-sdk-build authored Jan 18, 2024
1 parent bd330a8 commit 595c18e
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 333 deletions.
74 changes: 0 additions & 74 deletions box_sdk_gen/auth_schemas.py

This file was deleted.

260 changes: 161 additions & 99 deletions box_sdk_gen/ccg_auth.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,49 @@
from typing import Union, Optional
from typing import Optional

from .auth import Authentication
from .token_storage import TokenStorage, InMemoryTokenStorage
from .auth_schemas import (
TokenRequestBoxSubjectType,
TokenRequest,
TokenRequestGrantType,
)
from .fetch import fetch, FetchResponse, FetchOptions
from .network import NetworkSession
from .schemas import AccessToken
from typing import List

from box_sdk_gen.managers.authorization import RequestAccessTokenGrantType

from box_sdk_gen.managers.authorization import RequestAccessTokenSubjectTokenType

from box_sdk_gen.schemas import AccessToken

from box_sdk_gen.schemas import PostOAuth2TokenBoxSubjectTypeField

from box_sdk_gen.auth import Authentication

from box_sdk_gen.network import NetworkSession

from box_sdk_gen.token_storage import TokenStorage

from box_sdk_gen.token_storage import InMemoryTokenStorage

from box_sdk_gen.managers.authorization import AuthorizationManager


class CCGConfig:
def __init__(
self,
client_id: str,
client_secret: str,
enterprise_id: Union[None, str] = None,
user_id: Union[None, str] = None,
enterprise_id: Optional[str] = None,
user_id: Optional[str] = None,
token_storage: TokenStorage = None,
):
"""
:param client_id:
Box API key used for identifying the application the user is authenticating with.
:param client_secret:
Box API secret used for making auth requests.
:param enterprise_id:
The ID of the Box Developer Edition enterprise.
May be `None`, if the caller knows that it will not be
authenticating as an enterprise instance / service account.
If `user_id` is passed, this value is not used, unless
`authenticate_enterprise()` is called to authenticate as the enterprise instance.
:param user_id:
The user id to authenticate. This value is not required. But if it is provided, then the user
will be auto-authenticated at the time of the first API call.
Should be `None` if the intention is to authenticate as the
enterprise instance / service account. If both `enterprise_id` and
`user_id` are non-`None`, the `user` takes precedense when `refresh()`
is called.
<https://developer.box.com/en/guides/applications/>
<https://developer.box.com/en/guides/authentication/select/>
:param token_storage:
Object responsible for storing token. If no custom implementation provided,
the token will be stored in memory.
:param client_id: Box API key used for identifying the application the user is authenticating with
:type client_id: str
:param client_secret: Box API secret used for making auth requests.
:type client_secret: str
:param enterprise_id: The ID of the Box Developer Edition enterprise.
:type enterprise_id: Optional[str], optional
:param user_id: The user id to authenticate. This value is not required. But if it is provided, then the user will be auto-authenticated at the time of the first API call.
:type user_id: Optional[str], optional
:param token_storage: Object responsible for storing token. If no custom implementation provided,the token will be stored in memory.
:type token_storage: TokenStorage, optional
"""
if token_storage is None:
token_storage = InMemoryTokenStorage()
if not enterprise_id and not user_id:
raise Exception("Enterprise ID or User ID is needed")

self.client_id = client_id
self.client_secret = client_secret
self.enterprise_id = enterprise_id
Expand All @@ -62,89 +52,161 @@ def __init__(


class BoxCCGAuth(Authentication):
def __init__(self, config: CCGConfig):
def __init__(self, config: CCGConfig, **kwargs):
"""
:param config:
Configuration object of Client Credentials Grant auth.
:param config: Configuration object of Client Credentials Grant auth.
:type config: CCGConfig
"""
super().__init__(**kwargs)
self.config = config
self.token_storage = config.token_storage

if config.user_id:
self.subject_id = self.config.user_id
self.subject_type = TokenRequestBoxSubjectType.USER
else:
self.subject_type = TokenRequestBoxSubjectType.ENTERPRISE
self.subject_id = self.config.enterprise_id
self.token_storage = self.config.token_storage
self.subject_id = (
self.config.user_id
if not self.config.user_id == None
else self.config.enterprise_id
)
self.subject_type = 'user' if not self.config.user_id == None else 'enterprise'

def retrieve_token(
def refresh_token(
self, network_session: Optional[NetworkSession] = None
) -> AccessToken:
"""
Return a current token or get a new one when not available.
Get a new access token using CCG auth
:param network_session: An object to keep network session state
:return: Access token
:type network_session: Optional[NetworkSession], optional
"""
token = self.token_storage.get()
if token is None:
return self.refresh_token(network_session=network_session)
auth_manager: AuthorizationManager = (
AuthorizationManager(network_session=network_session)
if not network_session == None
else AuthorizationManager()
)
token: AccessToken = auth_manager.request_access_token(
grant_type=RequestAccessTokenGrantType.CLIENT_CREDENTIALS.value,
client_id=self.config.client_id,
client_secret=self.config.client_secret,
box_subject_type=self.subject_type,
box_subject_id=self.subject_id,
)
self.token_storage.store(token)
return token

def refresh_token(
def retrieve_token(
self, network_session: Optional[NetworkSession] = None
) -> AccessToken:
"""
Return a current token or get a new one when not available.
:param network_session: An object to keep network session state
:return: Access token
:type network_session: Optional[NetworkSession], optional
"""
request_body = TokenRequest(
grant_type=TokenRequestGrantType.CLIENT_CREDENTIALS,
client_id=self.config.client_id,
client_secret=self.config.client_secret,
box_subject_id=self.subject_id,
box_subject_type=self.subject_type,
)
old_token = self.token_storage.get()
if old_token == None:
new_token: AccessToken = self.refresh_token(network_session)
return new_token
return old_token

response: FetchResponse = fetch(
'https://api.box.com/oauth2/token',
FetchOptions(
method='POST',
data=request_body.to_dict(),
content_type='application/x-www-form-urlencoded',
network_session=network_session,
),
)

new_token = AccessToken.from_dict(response.data)
self.token_storage.store(new_token)
return new_token

def as_user(self, user_id: str):
def as_user(self, user_id: str, token_storage: TokenStorage = None) -> 'BoxCCGAuth':
"""
Set authentication as user. The new token will be automatically fetched with a next API call.
Create a new BoxCCGAuth instance that uses the provided user ID as the subject ID.
May be one of this application's created App User. Depending on the configured User Access Level, may also be any other App User or Managed User in the enterprise.
May be one of this application's created App User. Depending on the
configured User Access Level, may also be any other App User or Managed
User in the enterprise.
<https://developer.box.com/en/guides/applications/>
<https://developer.box.com/en/guides/authentication/select/>
:param user_id:
The id of the user to authenticate.
:param user_id: The id of the user to authenticate
:type user_id: str
:param token_storage: Object responsible for storing token in newly created BoxCCGAuth. If no custom implementation provided, the token will be stored in memory.
:type token_storage: TokenStorage, optional
"""
if token_storage is None:
token_storage = InMemoryTokenStorage()
new_config: CCGConfig = CCGConfig(
client_id=self.config.client_id,
client_secret=self.config.client_secret,
enterprise_id=self.config.enterprise_id,
user_id=user_id,
token_storage=token_storage,
)
return BoxCCGAuth(config=new_config)

def as_enterprise(
self, enterprise_id: str, token_storage: TokenStorage = None
) -> 'BoxCCGAuth':
"""
Create a new BoxCCGAuth instance that uses the provided enterprise ID as the subject ID.
:param enterprise_id: The id of the enterprise to authenticate
:type enterprise_id: str
:param token_storage: Object responsible for storing token in newly created BoxCCGAuth. If no custom implementation provided, the token will be stored in memory.
:type token_storage: TokenStorage, optional
"""
self.subject_id = user_id
self.subject_type = TokenRequestBoxSubjectType.USER
self.token_storage.clear()
if token_storage is None:
token_storage = InMemoryTokenStorage()
new_config: CCGConfig = CCGConfig(
client_id=self.config.client_id,
client_secret=self.config.client_secret,
enterprise_id=enterprise_id,
user_id=None,
token_storage=token_storage,
)
return BoxCCGAuth(config=new_config)

def as_enterprise(self, enterprise_id: str):
def downscope_token(
self,
scopes: List[str],
resource: Optional[str] = None,
shared_link: Optional[str] = None,
network_session: Optional[NetworkSession] = None,
) -> AccessToken:
"""
Downscope access token to the provided scopes. Returning a new access token with the provided scopes, with the original access token unchanged.
:param scopes: The scope(s) to apply to the resulting token.
:type scopes: List[str]
:param resource: The file or folder to get a downscoped token for. If None and shared_link None, the resulting token will not be scoped down to just a single item. The resource should be a full URL to an item, e.g. https://api.box.com/2.0/files/123456.
:type resource: Optional[str], optional
:param shared_link: The shared link to get a downscoped token for. If None and item None, the resulting token will not be scoped down to just a single item.
:type shared_link: Optional[str], optional
:param network_session: An object to keep network session state
:type network_session: Optional[NetworkSession], optional
"""
Set authentication as enterprise. The new token will be automatically fetched with a next API call.
token: Optional[AccessToken] = self.token_storage.get()
if token == None:
raise Exception(
'No access token is available. Make an API call to retrieve a token'
' before calling this method.'
)
auth_manager: AuthorizationManager = (
AuthorizationManager(network_session=network_session)
if not network_session == None
else AuthorizationManager()
)
downscoped_token: AccessToken = auth_manager.request_access_token(
grant_type=RequestAccessTokenGrantType.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE.value,
subject_token=token.access_token,
subject_token_type=RequestAccessTokenSubjectTokenType.URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN.value,
resource=resource,
scope=' '.join(scopes),
box_shared_link=shared_link,
)
return downscoped_token

:param enterprise_id:
The ID of the Box Developer Edition enterprise.
def revoke_token(self, network_session: Optional[NetworkSession] = None) -> None:
"""
self.subject_id = enterprise_id
self.subject_type = TokenRequestBoxSubjectType.ENTERPRISE
self.token_storage.clear()
Revoke the current access token and remove it from token storage.
:param network_session: An object to keep network session state
:type network_session: Optional[NetworkSession], optional
"""
old_token: Optional[AccessToken] = self.token_storage.get()
if old_token == None:
return None
auth_manager: AuthorizationManager = (
AuthorizationManager(network_session=network_session)
if not network_session == None
else AuthorizationManager()
)
auth_manager.revoke_access_token(
self.config.client_id, self.config.client_secret, old_token.access_token
)
return self.token_storage.clear()
Loading

0 comments on commit 595c18e

Please sign in to comment.