Skip to content

Commit

Permalink
Merge pull request #48 from best-doctor/bang-468-add-keycloak-oauth
Browse files Browse the repository at this point in the history
BANG-468: Use keycloak oauth
  • Loading branch information
Arondit authored Jul 24, 2024
2 parents b3657e7 + 4008310 commit 9279c00
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 8 deletions.
32 changes: 31 additions & 1 deletion auth/auth.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Dict, Union

import aiohttp_jinja2
from aiohttp import web
from aiohttp.web_exceptions import HTTPFound
from aiohttp_security import remember
from aiopg.sa.engine import Engine
from aiopg.sa.result import RowProxy
from aiohttp.web import Application
from aiohttp_security.abc import AbstractAuthorizationPolicy
from dynaconf import settings
from passlib.hash import sha256_crypt
from sqlalchemy import and_, func, not_

Expand All @@ -14,6 +19,31 @@
from typing import Optional


async def get_login_context(error: str | None = None) -> Dict[str, Union[str | bool]]:
use_oauth = getattr(getattr(settings, 'OAUTH', None), 'IS_USED', False)
only_oauth = getattr(getattr(settings, 'OAUTH', None), 'ONLY_OAUTH', False)
oauth_sign_in_title = getattr(getattr(settings, 'OAUTH', None), 'SIGN_IN_TITLE', '')
context = {
'context': '',
'use_oauth': use_oauth,
'only_oauth': only_oauth,
'oauth_sign_in_title': oauth_sign_in_title,
}
if error:
context['error'] = error
return context


async def oauth_on_login(request: web.Request, user_data: dict) -> web.Response:
await remember(request, HTTPFound('/zbs/switches'), 'admin')
return HTTPFound('/zbs/switches')


@aiohttp_jinja2.template('users/login.html')
async def oauth_on_error(request: web.Request) -> Dict[str, Union[str | bool]]:
return await get_login_context(error='OAUTH failed')


async def check_credentials(db_engine: Engine, username: str, password: str) -> bool:
"""Производит аутентификацию пользователя."""
async with db_engine.acquire() as conn:
Expand Down
20 changes: 14 additions & 6 deletions auth/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union

import aiohttp_jinja2
from aiohttp.abc import StreamResponse
from aiohttp.web import HTTPFound, View, Response
from aiohttp_security import forget, remember
from dynaconf import settings
from marshmallow.exceptions import ValidationError
from multidict import MultiDictProxy

from auth.auth import check_credentials
from auth.auth import check_credentials, get_login_context
from auth.schemes import LoginPostRequestSchema

if TYPE_CHECKING:
Expand All @@ -17,12 +18,16 @@

class LoginView(View):
@aiohttp_jinja2.template('users/login.html')
async def get(self, error: Optional[str] = None) -> Dict[str, str]:
return {'context': ''}
async def get(self, error: Optional[str] = None) -> Dict[str, Union[str | bool]]:
return await get_login_context()

@aiohttp_jinja2.template('users/login.html')
async def error(self) -> Dict[str, str]:
return {'context': '', 'error': 'Authorization failed'}
async def error(self) -> Dict[str, Union[str | bool]]:
return await get_login_context('Authorization failed')

@aiohttp_jinja2.template('users/login.html')
async def only_oauth_error(self) -> Dict[str, Union[str | bool]]:
return await get_login_context('Classic login is forbidden')

async def authorise(
self, response_location: Response, login: str, password: str,
Expand All @@ -33,6 +38,9 @@ async def authorise(
return await self.error()

async def post(self) -> StreamResponse:
only_oauth = getattr(getattr(settings, 'OAUTH', None), 'ONLY_OAUTH', False)
if only_oauth:
return await self.only_oauth_error()
response_location = HTTPFound('/zbs/switches')
form_data = await self.request.post()
validated_data = self.validate_form_data(form_data)
Expand Down
17 changes: 16 additions & 1 deletion its_on/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pathlib

from aiohttp import web
from aiohttp_oauth2 import oauth2_app
from aiohttp_security import setup as setup_security
from aiohttp_security import SessionIdentityPolicy
import aiohttp_cors
Expand All @@ -16,7 +17,7 @@
from dynaconf import settings
import uvloop

from auth.auth import DBAuthorizationPolicy
from auth.auth import DBAuthorizationPolicy, oauth_on_login, oauth_on_error
from its_on.cache import setup_cache
from its_on.db_utils import init_pg, close_pg
from its_on.middlewares import setup_middlewares
Expand All @@ -42,6 +43,20 @@ def init_app(
) -> web.Application:
app = web.Application(loop=loop)

if settings.OAUTH.IS_USED:
app.add_subapp(
'/oauth/',
oauth2_app(
client_id=settings.OAUTH.CLIENT_ID,
client_secret=settings.OAUTH.CLIENT_SECRET,
authorize_url=settings.OAUTH.AUTHORIZE_URL,
token_url=settings.OAUTH.TOKEN_URL,
on_login=oauth_on_login,
on_error=oauth_on_error,
json_data=False,
),
)

app['config'] = settings

if not redis_pool:
Expand Down
7 changes: 7 additions & 0 deletions its_on/templates/users/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<div class="container">
<h2>ITS ON</h2>
<br>
{%- if not only_oauth -%}
<form class="form-horizontal" action="/zbs/login" method="post">
<div class="form-group">
<label class="control-label col-sm-2" for="email">Login:</label>
Expand All @@ -31,6 +32,12 @@ <h2>ITS ON</h2>
</div>
</div>
</form>
{%- endif -%}
{%- if use_oauth -%}
<div class="form-group">
<a href="/oauth/auth" class="col-sm-offset-2 btn btn-default"> {{ oauth_sign_in_title }} </a>
</div>
{%- endif -%}
{%- if error is defined -%}
<div class="alert alert-danger" role="alert">{{ error }}</div>
{%- endif -%}
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ aiohttp-cors==0.7.0
aiohttp-jinja2==1.5.0
aiohttp-security==0.4.0
aiohttp-session==2.8.0
aiohttp-oauth2==0.0.5
aiodns==2.0.0
aiopg==1.0.0
aioredis==1.3.1
Expand Down
8 changes: 8 additions & 0 deletions settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ default:
port: 8081
database:
dsn: postgresql://bestdoctor:bestdoctor@localhost:5432/its_on
oauth:
is_used: false
only_oauth: false
client_id: '@none'
client_secret: '@none'
authorize_url: '@none'
token_url: '@none'
sign_in_title: 'Sign in with "Bestdoctor ID"'
enable_db_logging: false
cache_url: redis://127.0.0.1:6379/1
cache_ttl: 300
Expand Down

0 comments on commit 9279c00

Please sign in to comment.