Skip to content

Commit

Permalink
feat: Added ability to use Client Credentials Grant (#39)
Browse files Browse the repository at this point in the history
* feat:Added getting token using client credentials

- Added getting access token using client credentials grant.
  Use get_oauth_token() without params
- Minor logic refactoring

* fix:Fixed linter issue
  • Loading branch information
SergeyDyudin authored Feb 10, 2023
1 parent 8b21c83 commit fee0fe0
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 44 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ casdoor-python-sdk support basic user operations, like:
- `enforce(self, permission_model_name: str, sub: str, obj: str, act: str)`, check permission from model


## Also. Resource Owner Password Credentials Grant
## Resource Owner Password Credentials Grant

If your application doesn't have a frontend that redirects users to Casdoor and you have Password Credentials Grant enabled, then you may get access token like this:

Expand All @@ -125,4 +125,16 @@ access_token = sdk.get_oauth_token(username=username, password=password)
decoded_msg = sdk.parse_jwt_token(access_token)
```

`decoded_msg` is the JSON data decoded from the `access_token`, which contains user info and other useful stuff.
`decoded_msg` is the JSON data decoded from the `access_token`, which contains user info and other useful stuff.

## Client Credentials Grant

You can also use Client Credentials Grant when your application does not have a frontend.
It is important to note that the AccessToken obtained in this way differs from other in that it corresponds to the application rather than to the user.

```python
access_token = sdk.get_oauth_token()
decoded_msg = sdk.parse_jwt_token(access_token)
```

`decoded_msg` is the JSON data decoded from the `access_token`.
47 changes: 30 additions & 17 deletions src/casdoor/async_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,15 @@ async def get_oauth_token(
"""
Request the Casdoor server to get access_token.
Must be set code or username and password for grant type.
If nothing is set then client credentials grant will be used.
:param code: the code that sent from Casdoor using redirect url
back to your server.
:param username: Casdoor username
:param password: username password
:return: access token: str
"""
payload = self._get_payload_for_access_token_request(
code=code,
username=username,
password=password
)
response = await self._oath_token_request(payload)
response = await self.oauth_token_request(code, username, password)
access_token = response.get("access_token")

return access_token
Expand All @@ -122,8 +118,7 @@ def _get_payload_for_access_token_request(
password=password
)
else:
raise ValueError("Attributes for some grant type must be set"
"(code or username and password)")
return self.__get_payload_for_client_credentials()

def __get_payload_for_authorization_code(self, code: str) -> dict:
"""
Expand Down Expand Up @@ -153,24 +148,42 @@ def __get_payload_for_password_credentials(
"password": password
}

async def oauth_token_request(self, code: str) -> dict:
def __get_payload_for_client_credentials(self) -> dict:
"""
Return payload for auth request with client credentials.
"""
return {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
}

async def oauth_token_request(
self,
code: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None
) -> dict:
"""
Request the Casdoor server to get access_token.
Must be set code or username and password for grant type.
If nothing is set then client credentials grant will be used.
Returns full response as dict.
:param code: the code that sent from Casdoor using redirect url
back to your server.
:param username: Casdoor username
:param password: username password
:return: Response from Casdoor
"""
params = {
"grant_type": self.grant_type,
"client_id": self.client_id,
"client_secret": self.client_secret,
"code": code,
}
return await self._oath_token_request(payload=params)
params = self._get_payload_for_access_token_request(
code=code,
username=username,
password=password
)
return await self._oauth_token_request(payload=params)

async def _oath_token_request(self, payload: dict) -> dict:
async def _oauth_token_request(self, payload: dict) -> dict:
"""
Request the Casdoor server to get access_token.
Expand Down
56 changes: 35 additions & 21 deletions src/casdoor/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,17 @@ def get_oauth_token(
password: Optional[str] = None
) -> str:
"""
Request the Casdoor server to get access_token. Must be set code or
username and password for grant type.
Request the Casdoor server to get access_token.
Must be set code or username and password for grant type.
If nothing is set then client credentials grant will be used.
:param code: the code that sent from Casdoor using redirect url back
to your server.
:param username: casdoor username
:param password: username password
:return: access_token: str
"""
payload = self._get_payload_for_access_token_request(
code=code,
username=username,
password=password
)
response = self._oath_token_request(payload)
response = self.oauth_token_request(code, username, password)
access_token = response.json().get("access_token")

return access_token
Expand All @@ -116,8 +112,7 @@ def _get_payload_for_access_token_request(
password=password
)
else:
raise ValueError("Attributes for some grant type must be set"
"(code or username and password)")
return self.__get_payload_for_client_credentials()

def __get_payload_for_authorization_code(self, code: str) -> dict:
"""
Expand Down Expand Up @@ -147,23 +142,42 @@ def __get_payload_for_password_credentials(
"password": password
}

def oauth_token_request(self, code: str) -> requests.Response:
def __get_payload_for_client_credentials(self) -> dict:
"""
Request the Casdoor server to get access_token.
:param code: the code that sent from Casdoor using redirect
url back to your server.
:return: Response from Casdoor
Return payload for auth request with client credentials.
"""
params = {
"grant_type": self.grant_type,
return {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"code": code,
}
return self._oath_token_request(payload=params)

def _oath_token_request(self, payload: dict) -> requests.Response:
def oauth_token_request(
self,
code: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None
) -> requests.Response:
"""
Request the Casdoor server to get access_token.
Must be set code or username and password for grant type.
If nothing is set then client credentials grant will be used.
Returns full response as dict.
:param code: the code that sent from Casdoor using redirect url
back to your server.
:param username: Casdoor username
:param password: username password
:return: Response from Casdoor
"""
params = self._get_payload_for_access_token_request(
code=code,
username=username,
password=password
)
return self._oauth_token_request(payload=params)

def _oauth_token_request(self, payload: dict) -> requests.Response:
"""
Request the Casdoor server to get access_token.
Expand Down
19 changes: 17 additions & 2 deletions src/tests/test_async_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ def get_sdk():
)
return sdk

async def test__oath_token_request(self):
async def test__oauth_token_request(self):
sdk = self.get_sdk()
data = {
"grant_type": sdk.grant_type,
"client_id": sdk.client_id,
"client_secret": sdk.client_secret,
"code": self.code,
}
response = await sdk._oath_token_request(payload=data)
response = await sdk._oauth_token_request(payload=data)
self.assertIsInstance(response, dict)

async def test__get_payload_for_authorization_code(self):
Expand All @@ -73,6 +73,11 @@ async def test__get_payload_for_password_credentials(self):
)
self.assertEqual("password", result.get("grant_type"))

async def test__get_payload_for_client_credentials(self):
sdk = self.get_sdk()
result = sdk._AsyncCasdoorSDK__get_payload_for_client_credentials() # noqa: It's private method
self.assertEqual("client_credentials", result.get("grant_type"))

async def test__get_payload_for_access_token_request_with_code(self):
sdk = self.get_sdk()
result = sdk._get_payload_for_access_token_request(code="test")
Expand All @@ -86,6 +91,11 @@ async def test__get_payload_for_access_token_request_with_cred(self):
)
self.assertEqual("password", result.get("grant_type"))

async def test_get_payload_for_access_token_request_with_client_cred(self):
sdk = self.get_sdk()
result = sdk._get_payload_for_access_token_request()
self.assertEqual("client_credentials", result.get("grant_type"))

async def test_get_oauth_token_with_password(self):
sdk = self.get_sdk()
access_token = await sdk.get_oauth_token(
Expand All @@ -94,6 +104,11 @@ async def test_get_oauth_token_with_password(self):
)
self.assertIsInstance(access_token, str)

async def test_get_oauth_token_with_client_cred(self):
sdk = self.get_sdk()
access_token = await sdk.get_oauth_token()
self.assertIsInstance(access_token, str)

async def test_get_oauth_token(self):
sdk = self.get_sdk()
access_token = await sdk.get_oauth_token(code=self.code)
Expand Down
19 changes: 17 additions & 2 deletions src/tests/test_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ def get_sdk():
)
return sdk

def test__oath_token_request(self):
def test__oauth_token_request(self):
sdk = self.get_sdk()
data = {
"grant_type": sdk.grant_type,
"client_id": sdk.client_id,
"client_secret": sdk.client_secret,
"code": self.code,
}
response = sdk._oath_token_request(payload=data)
response = sdk._oauth_token_request(payload=data)
self.assertIsInstance(response, dict)

def test__get_payload_for_authorization_code(self):
Expand All @@ -67,6 +67,11 @@ def test__get_payload_for_authorization_code(self):
)
self.assertEqual("authorization_code", result.get("grant_type"))

def test__get_payload_for_client_credentials(self):
sdk = self.get_sdk()
result = sdk._CasdoorSDK__get_payload_for_client_credentials() # noqa: It's private method
self.assertEqual("client_credentials", result.get("grant_type"))

def test__get_payload_for_password_credentials(self):
sdk = self.get_sdk()
result = sdk._CasdoorSDK__get_payload_for_password_credentials( # noqa: It's private method
Expand All @@ -80,6 +85,11 @@ def test__get_payload_for_access_token_request_with_code(self):
result = sdk._get_payload_for_access_token_request(code="test")
self.assertEqual("authorization_code", result.get("grant_type"))

def test__get_payload_for_access_token_request_with_client_cred(self):
sdk = self.get_sdk()
result = sdk._get_payload_for_access_token_request()
self.assertEqual("client_credentials", result.get("grant_type"))

def test__get_payload_for_access_token_request_with_cred(self):
sdk = self.get_sdk()
result = sdk._get_payload_for_access_token_request(
Expand All @@ -88,6 +98,11 @@ def test__get_payload_for_access_token_request_with_cred(self):
)
self.assertEqual("password", result.get("grant_type"))

def test_get_oauth_token_with_client_cred(self):
sdk = self.get_sdk()
access_token = sdk.get_oauth_token()
self.assertIsInstance(access_token, str)

def test_get_oauth_token_with_code(self):
sdk = self.get_sdk()
access_token = sdk.get_oauth_token(code=self.code)
Expand Down

0 comments on commit fee0fe0

Please sign in to comment.