Skip to content

Commit

Permalink
AAI completely implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
costero-e committed Mar 28, 2024
1 parent faa4bbb commit cf53abb
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 47 deletions.
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@ docker exec beacon-permissions bash permissions/permissions-ui/start.sh
Please, bear in mind that the name of the user has to be the same that you used when creating the user in LS or in IDP, whatever the AAI method you are working with.
To give a user a certain type of response for their queries, please modify this file [response_type.yml](https://github.com/EGA-archive/beacon2-ri-api/blob/master/beacon/request/response_type.yml) adding the maximum type of response you want to allow every user.

Also, you will need to edit the file [conf.py](beacon/conf.py) and introduce the domain where your keycloak is being hosted inside **idp_user_info** and the issuer you trust for your token inside **idp_issuer**. In case you want to run your local container, use this configuration:
```bash
idp_issuer='https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon'
idp_user_info = 'https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon/protocol/openid-connect/userinfo'
lsaai_issuer='https://login.elixir-czech.org/oidc/'
lsaai_user_info = 'https://login.elixir-czech.org/oidc/userinfo'
```
Also, you will need to edit the file [conf.py](beacon/conf.py) and introduce the domain where your keycloak is being hosted inside **idp_url**.

Also, inside the folder permissions, before building your permissions container, you will need to create an .env file and add the CLIENT_ID for your LSAAI or Keycloak or both, with these same variable names:
```bash
LSAAI_CLIENT_ID='your_lsaai_client_id'
LSAAI_CLIENT_SECRET='your_lsaai_client_secret'
KEYCLOAK_CLIENT_ID='your_keycloak_client_id'
KEYCLOAK_CLIENT_SECRET='your_keycloak_client_secret'
```
When you have your access token, pass it in a header with **Authorization: Bearer** in your POST request to get your answers. This token works coming from either from LS AAI or from keycloak (idp container).

Expand Down
6 changes: 2 additions & 4 deletions beacon/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,9 @@
#
# or use Elixir AAI (see https://elixir-europe.org/services/compute/aai)
#
idp_issuer='https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon'
idp_user_info = 'https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon/protocol/openid-connect/userinfo'
lsaai_issuer='https://login.elixir-czech.org/oidc/'
lsaai_user_info = 'https://login.elixir-czech.org/oidc/userinfo'

idp_url = 'http://idp:8080/'
#idp_url = 'http://localhost:8080/'

#
# UI
Expand Down
9 changes: 1 addition & 8 deletions beacon/utils/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import asyncio

from beacon.db.datasets import filter_public_datasets
from ..conf import permissions_url, idp_user_info as idpu, lsaai_user_info as lsu
from ..conf import permissions_url

LOG = logging.getLogger(__name__)

Expand All @@ -32,13 +32,6 @@ async def resolve_token(token, requested_datasets_ids):
'Accept': 'application/json'},
json={'datasets': requested_datasets_ids}, # will set the Content-Type to application/json
) as resp:
'''
if resp.status > 200:
LOG.error('Permissions server error %d', resp.status)
error = await resp.text()
LOG.error('Error: %s', error)
raise web.HTTPUnauthorized(body=error)
'''
content = await resp.content.read()
content = content.decode('utf-8')
content_splitted= content.split(':')
Expand Down
6 changes: 2 additions & 4 deletions deploy/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,9 @@
#
# or use Elixir AAI (see https://elixir-europe.org/services/compute/aai)
#
idp_issuer='https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon'
idp_user_info = 'https://beacon-network-demo2.ega-archive.org/auth/realms/Beacon/protocol/openid-connect/userinfo'
lsaai_issuer='https://login.elixir-czech.org/oidc/'
lsaai_user_info = 'https://login.elixir-czech.org/oidc/userinfo'

idp_url = 'http://idp:8080/'
#idp_url = 'http://localhost:8080/'

#
# UI
Expand Down
101 changes: 77 additions & 24 deletions permissions/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,52 +22,112 @@
load_dotenv()

LSAAI_CLIENT_ID = os.getenv('LSAAI_CLIENT_ID')
LSAAI_CLIENT_SECRET = os.getenv('LSAAI_CLIENT_SECRET')
KEYCLOAK_CLIENT_ID = os.getenv('KEYCLOAK_CLIENT_ID')

KEYCLOAK_CLIENT_SECRET = os.getenv('KEYCLOAK_CLIENT_SECRET')

LOG = logging.getLogger(__name__)

keycloak_user_info = conf.idp_url + 'auth/realms/Beacon/protocol/openid-connect/userinfo'
keycloak_introspection = conf.idp_url + 'auth/realms/Beacon/protocol/openid-connect/token/introspect'
keycloak_issuer = conf.idp_url + 'auth/realms/Beacon'
keycloak_jwks_url = conf.idp_url + 'auth/realms/Beacon/protocol/openid-connect/certs'
lsaai_user_info = 'https://login.elixir-czech.org/oidc/userinfo'
lsaai_introspection = 'https://login.elixir-czech.org/oidc/introspect'
lsaai_issuer = 'https://login.elixir-czech.org/oidc/'
lsaai_jwks_url = "https://login.elixir-czech.org/oidc/jwk"

idp_user_info = conf.idp_user_info
lsaai_user_info = conf.lsaai_user_info
def validate_access_token(access_token, idp_issuer, jwks_url, algorithm, aud):
if not jwt.algorithms.has_crypto:
print("No crypto support for JWT, please install the cryptography dependency")
return False

#idp_user_info = 'http://localhost:8080/oidc/userinfo'
#idp_user_info = 'http://ls-aai-mock:8080/oidc/userinfo'
#idp_user_info = 'http://idp:8080/auth/realms/Beacon/protocol/openid-connect/userinfo'
#idp_introspection = 'http://ls-aai-mock:8080/oidc/introspect'
#idp_introspection = 'http://idp:8080/auth/realms/Beacon/protocol/openid-connect/token/introspect'
#idp_user_info = 'http://idp:8080/auth/realms/Beacon/protocol/openid-connect/userinfo'
#idp_introspection = 'http://idp:8080/auth/realms/Beacon/protocol/openid-connect/token/introspect'
try:
jwks_client = jwt.PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=360)
signing_key = jwks_client.get_signing_key_from_jwt(access_token)
data = jwt.decode(
access_token,
signing_key.key,
algorithms=[algorithm],
issuer=idp_issuer,
audience=aud,
options={
"verify_signature": True,
"verify_exp": True,
"verify_nbf": True,
"verify_iat": True,
"verify_aud": True,
"verify_iss": True,
},
)
LOG.error(data)
return True
except jwt.exceptions.PyJWTError as err:
LOG.error(f"Error: {err}")
return False

async def get_user_info(access_token):
'''
We use the access_token to get the user info.
On failure (ie an invalid token), we try to get an explanation.
'''
LOG.debug('Token: %s', access_token)

access_token_validation = False
try:
header = jwt.get_unverified_header(access_token)
algorithm=header["alg"]
decoded = jwt.decode(access_token, options={"verify_signature": False})
LOG.error(decoded)
issuer = decoded['iss']
audience = decoded['aud']
aud = decoded['aud']
list_visa_datasets=[]
visa_datasets=None
except Exception:
user = 'public'
return user
LOG.error(issuer)
user_info=''
if issuer == conf.lsaai_issuer and audience == LSAAI_CLIENT_ID:
if issuer == lsaai_issuer and aud == LSAAI_CLIENT_ID:
user_info = lsaai_user_info
elif issuer == conf.idp_issuer and audience == KEYCLOAK_CLIENT_ID:
user_info = idp_user_info
idp_issuer = 'https://login.elixir-czech.org/oidc/'
idp_introspection = lsaai_introspection
idp_client_id = LSAAI_CLIENT_ID
idp_client_secret = LSAAI_CLIENT_SECRET
idp_jwks_url = lsaai_jwks_url
elif issuer == keycloak_issuer and aud == KEYCLOAK_CLIENT_ID:
user_info = keycloak_user_info
idp_issuer = keycloak_issuer
idp_introspection = keycloak_introspection
idp_client_id = KEYCLOAK_CLIENT_ID
idp_client_secret = KEYCLOAK_CLIENT_SECRET
idp_jwks_url = keycloak_jwks_url
else:
raise web.HTTPUnauthorized('invalid token')



access_token_validation = validate_access_token(access_token, idp_issuer, idp_jwks_url, algorithm, aud)
if access_token_validation is False:
raise web.HTTPUnauthorized('invalid token')

LOG.error(user_info)
user = None


async with ClientSession() as session:
async with session.post(idp_introspection,
auth=BasicAuth(idp_client_id, password=idp_client_secret),
data=FormData({ 'token': access_token, 'token_type_hint': 'access_token' }, charset='UTF-8')
) as resp:
LOG.error('Response %s', resp.status)
#LOG.debug('Response %s', resp)
content = await resp.text()
LOG.error('Content: %s', content)
if resp.status == 200:
pass
else:
LOG.error('Invalid token')
user = 'public'
return user, list_visa_datasets

async with ClientSession(trust_env=True) as session:
headers = { 'Accept': 'application/json', 'Authorization': 'Bearer ' + access_token }
LOG.error('Contacting %s', user_info)
Expand All @@ -81,12 +141,6 @@ async def get_user_info(access_token):
for visa_dataset in visa_datasets:
try:
visa = jwt.decode(visa_dataset, options={"verify_signature": False}, algorithms=["RS256"])
if visa['iss']==conf.lsaai_issuer:
pass
elif visa['iss']==conf.idp_issuer:
pass
else:
raise web.HTTPUnauthorized('invalid token')
dataset_url = visa["ga4gh_visa_v1"]["value"]
dataset_url_splitted = dataset_url.split('/')
visa_dataset = dataset_url_splitted[-1]
Expand All @@ -104,7 +158,6 @@ async def get_user_info(access_token):
user = 'public'
return user, list_visa_datasets


def bearer_required(func):

async def decorated(request):
Expand Down

0 comments on commit cf53abb

Please sign in to comment.