Skip to content

Commit

Permalink
feat: Appended asynchronous SDK version (#33)
Browse files Browse the repository at this point in the history
* Fix init imports

* Refresh token was appended
request for access_token export to new method

* Codestyle was fixed

* Codestyle was fixed

* Codestyle was fixed

* Codestyle was fixed
New method appended to Readme

* feat: Appended asynchronous SDK version
  • Loading branch information
Verdgil authored Dec 12, 2022
1 parent 1efa85e commit 92a6010
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 5 deletions.
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Initialization requires 5 parameters, which are all str type:
| Name (in order) | Must | Description |
| ---------------- | ---- | --------------------------------------------------- |
| endpoint | Yes | Casdoor Server Url, such as `http://localhost:8000` |
| client_id | Yes | Application.client_id |
| client_secret | Yes | Application.client_secret |
| certificate | Yes | Same as Casdoor certificate |
| org_name | Yes |Organization name
| client_id | Yes | Application.client_id |
| client_secret | Yes | Application.client_secret |
| certificate | Yes | Same as Casdoor certificate |
| org_name | Yes | Organization name |

```python
from casdoor import CasdoorSDK
Expand All @@ -42,6 +42,27 @@ sdk = CasdoorSDK(
org_name,
)
```

OR use async version

```python
from casdoor import AsyncCasdoorSDK

certificate = b'''-----BEGIN CERTIFICATE-----
MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh
...
-----END CERTIFICATE-----'''

sdk = AsyncCasdoorSDK(
endpoint,
client_id,
client_secret,
certificate,
org_name,
)
```


## Step2. Authorize with the Casdoor server
At this point, we should use some ways to verify with the Casdoor server.

Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ install_requires =
requests
pyjwt
cryptography
aiohttp
python_requires = >=3.6
test_suite = tests

Expand Down
1 change: 1 addition & 0 deletions src/casdoor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .main import CasdoorSDK
from .async_main import AsyncCasdoorSDK
from .user import User
199 changes: 199 additions & 0 deletions src/casdoor/async_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# Copyright 2021 The Casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
import aiohttp
import jwt
import json
from .user import User
from typing import List
from cryptography import x509
from cryptography.hazmat.backends import default_backend


class AsyncCasdoorSDK:
def __init__(
self,
endpoint: str,
client_id: str,
client_secret: str,
certificate: str,
org_name: str,
application_name: str,
front_endpoint: str = None
):
self.endpoint = endpoint
if front_endpoint:
self.front_endpoint = front_endpoint
else:
self.front_endpoint = endpoint.replace(":8000", ":7001")
self.client_id = client_id
self.client_secret = client_secret
self.certificate = certificate
self.org_name = org_name
self.application_name = application_name
self.grant_type = "authorization_code"

self.algorithms = ["RS256"]
self._session = aiohttp.ClientSession()

def __del__(self):
loop = asyncio.get_running_loop()
loop.create_task(self._session.close())

@property
def certification(self) -> bytes:
if type(self.certificate) is not str:
raise TypeError('certificate field must be str type')
return self.certificate.encode('utf-8')

async def get_auth_link(self, redirect_uri: str, response_type: str = "code", scope: str = "read"):
url = self.front_endpoint + "/login/oauth/authorize"
params = {
"client_id": self.client_id,
"response_type": response_type,
"redirect_uri": redirect_uri,
"scope": scope,
"state": self.application_name,
}
async with self._session.request("", url, params=params) as request:
return request.url

async def get_oauth_token(self, code: str) -> str:
"""
Request the Casdoor server to get access_token.
:param code: the code that sent from Casdoor using redirect url back to your server.
:return: access_token
"""
r = await self.oauth_token_request(code)
access_token = r.get("access_token")
return access_token

async def oauth_token_request(self, code: str) -> 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
"""
url = self.endpoint + "/api/login/oauth/access_token"
params = {
"grant_type": self.grant_type,
"client_id": self.client_id,
"client_secret": self.client_secret,
"code": code,
}
async with self._session.post(url, data=params) as response:
return await response.json()

async def refresh_token_request(self, refresh_token: str, scope: str = "") -> dict:
"""
Request the Casdoor server to get access_token.
:param refresh_token: refresh_token for send to Casdoor
:param scope: OAuth scope
:return: Response from Casdoor
"""
url = self.endpoint + "/api/login/oauth/refresh_token"
params = {
"grant_type": self.grant_type,
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": scope,
"refresh_token": refresh_token,
}
async with self._session.post(url, data=params) as request:
return await request.json()

async def refresh_oauth_token(self, refresh_token: str, scope: str = "") -> str:
"""
Request the Casdoor server to get access_token.
:param refresh_token: refresh_token for send to Casdoor
:param scope: OAuth scope
:return: Response from Casdoor
"""
r = await self.refresh_token_request(refresh_token, scope)
access_token = r.get("access_token")

return access_token

def parse_jwt_token(self, token: str) -> dict:
"""
Converts the returned access_token to real data using jwt (JSON Web Token) algorithms.
:param token: access_token
:return: the data in dict format
"""
certificate = x509.load_pem_x509_certificate(self.certification, default_backend())

return_json = jwt.decode(
token,
certificate.public_key(),
algorithms=self.algorithms,
audience=self.client_id,
)
return return_json

async def get_users(self) -> List[dict]:
"""
Get the users from Casdoor.
:return: a list of dicts containing user info
"""
url = self.endpoint + "/api/get-users"
params = {
"owner": self.org_name,
"clientId": self.client_id,
"clientSecret": self.client_secret,
}
async with self._session.get(url, params=params) as request:
users = await request.json()
return users

async def get_user(self, user_id: str) -> dict:
"""
Get the user from Casdoor providing the user_id.
:param user_id: the id of the user
:return: a dict that contains user's info
"""
url = self.endpoint + "/api/get-user"
params = {
"id": f"{self.org_name}/{user_id}",
"clientId": self.client_id,
"clientSecret": self.client_secret,
}
async with self._session.get(url, params=params) as request:
user = await request.json()
return user

async def modify_user(self, method: str, user: User) -> dict:
url = self.endpoint + f"/api/{method}"
user.owner = self.org_name
params = {
"id": f"{user.owner}/{user.name}",
"clientId": self.client_id,
"clientSecret": self.client_secret,
}
user_info = json.dumps(user.to_dict())
async with self._session.post(url, params=params, data=user_info) as request:
response = await request.json()
return response

async def add_user(self, user: User) -> dict:
response = await self.modify_user("add-user", user)
return response

async def update_user(self, user: User) -> dict:
response = await self.modify_user("update-user", user)
return response

async def delete_user(self, user: User) -> dict:
response = await self.modify_user("delete-user", user)
return response
107 changes: 107 additions & 0 deletions src/tests/test_async_oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Copyright 2021 The Casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from src.casdoor.async_main import AsyncCasdoorSDK, User
from unittest import IsolatedAsyncioTestCase


class TestOAuth(IsolatedAsyncioTestCase):
"""
You should replace the code content below and
the get_sdk() method's content with your own Casdoor
instance and such if you need to. And running these tests successfully
proves that your connection to Casdoor is good-and-working!
"""

# server returned authorization code
code = "6d038ac60d4e1f17e742"

@staticmethod
def get_sdk():

sdk = AsyncCasdoorSDK(
endpoint="http://test.casbin.com:8000",
client_id="3267f876b11e7d1cb217",
client_secret="3f0d1f06d28d65309c8f38b505cb9dcfa487754d",
certificate="CasdoorSecret",
org_name="built-in",
application_name="app-built-in"
)
return sdk

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

async def test_oauth_token_request(self):
sdk = self.get_sdk()
response = await sdk.oauth_token_request(self.code)
self.assertIsInstance(response, dict)

async def test_refresh_token_request(self):
sdk = self.get_sdk()
response = await sdk.oauth_token_request(self.code)
refresh_token = response.get("refresh_token")
sdk.grant_type = "refresh_token"
response = await sdk.refresh_token_request(refresh_token)
sdk.grant_type = "authorization_code"
self.assertIsInstance(response, dict)

async def test_get_oauth_refreshed_token(self):
sdk = self.get_sdk()
response = await sdk.oauth_token_request(self.code)
refresh_token = response.get("refresh_token")
sdk.grant_type = "refresh_token"
response = await sdk.refresh_oauth_token(refresh_token)
sdk.grant_type = "authorization_code"
self.assertIsInstance(response, str)

async def test_parse_jwt_token(self):
sdk = self.get_sdk()
access_token = await sdk.get_oauth_token(self.code)
decoded_msg = sdk.parse_jwt_token(access_token)
self.assertIsInstance(decoded_msg, dict)

async def test_get_users(self):
sdk = self.get_sdk()
users = await sdk.get_users()
self.assertIsInstance(users, list)

async def test_get_user(self):
sdk = self.get_sdk()
user = await sdk.get_user("admin")
self.assertIsInstance(user, dict)

async def test_modify_user(self):
sdk = self.get_sdk()
user = User()
user.name = "test_ffyuanda"
await sdk.delete_user(user)

response = await sdk.add_user(user)
self.assertEqual(response["data"], "Affected")

response = await sdk.delete_user(user)
self.assertEqual(response["data"], "Affected")

response = await sdk.add_user(user)
self.assertEqual(response["data"], "Affected")

user.phone = "phone"
response = await sdk.update_user(user)
self.assertEqual(response["data"], "Affected")

self.assertIn("status", response)
self.assertIsInstance(response, dict)
4 changes: 3 additions & 1 deletion src/tests/test_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def get_sdk():
client_secret="3f0d1f06d28d65309c8f38b505cb9dcfa487754d",
certificate="CasdoorSecret",
org_name="built-in",
application_name= "app-built-in"
application_name="app-built-in"
)
return sdk

Expand All @@ -62,7 +62,9 @@ def test_get_oauth_refreshed_token(self):
sdk = self.get_sdk()
response = sdk.oauth_token_request(self.code)
refresh_token = response.json().get("refresh_token")
sdk.grant_type = "refresh_token"
response = sdk.refresh_oauth_token(refresh_token)
sdk.grant_type = "authorization_code"
self.assertIsInstance(response, str)

def test_parse_jwt_token(self):
Expand Down

0 comments on commit 92a6010

Please sign in to comment.