tailbone/tailbone/api/auth.py
Lance Edgar a0bb481a43 Use AuthHandler.get_permissions()
instead of deprecated `cache_permissions()`
2022-01-03 15:34:00 -06:00

214 lines
7 KiB
Python

# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Tailbone Web API - Auth Views
"""
from __future__ import unicode_literals, absolute_import
from rattail.db.auth import set_user_password
from cornice import Service
from tailbone.api import APIView, api
from tailbone.db import Session
from tailbone.auth import login_user, logout_user
class AuthenticationView(APIView):
@api
def check_session(self):
"""
View to serve as "no-op" / ping action to check current user's session.
This will establish a server-side web session for the user if none
exists. Note that this also resets the user's session timer.
"""
data = {'ok': True}
if self.request.user:
data['user'] = self.get_user_info(self.request.user)
data['permissions'] = list(self.request.tailbone_cached_permissions)
# background color may be set per-request, by some apps
if hasattr(self.request, 'background_color') and self.request.background_color:
data['background_color'] = self.request.background_color
else: # otherwise we use the one from config
data['background_color'] = self.rattail_config.get(
'tailbone', 'background_color')
return data
@api
def login(self):
"""
API login view.
"""
if self.request.method == 'OPTIONS':
return self.request.response
username = self.request.json.get('username')
password = self.request.json.get('password')
if not (username and password):
return {'error': "Invalid username or password"}
# make sure credentials are valid
user = self.authenticate_user(username, password)
if not user:
return {'error': "Invalid username or password"}
# is there some reason this user should not login?
error = self.why_cant_user_login(user)
if error:
return {'error': error}
app = self.get_rattail_app()
auth = app.get_auth_handler()
login_user(self.request, user)
return {
'ok': True,
'user': self.get_user_info(user),
'permissions': list(auth.get_permissions(Session(), user)),
}
def authenticate_user(self, username, password):
app = self.get_rattail_app()
auth = app.get_auth_handler()
return auth.authenticate_user(Session(), username, password)
def why_cant_user_login(self, user):
"""
This method is given a ``User`` instance, which represents someone who
is just now trying to login, and has already cleared the basic hurdle
of providing the correct credentials for a user on file. This method
is responsible then, for further verification that this user *should*
in fact be allowed to login to this app node. If the method determines
a reason the user should *not* be allowed to login, then it should
return that reason as a simple string.
"""
@api
def logout(self):
"""
API logout view.
"""
if self.request.method == 'OPTIONS':
return self.request.response
logout_user(self.request)
return {'ok': True}
@api
def become_root(self):
"""
Elevate the current request to 'root' for full system access.
"""
if not self.request.is_admin:
raise self.forbidden()
self.request.user.record_event(self.enum.USER_EVENT_BECOME_ROOT)
self.request.session['is_root'] = True
return {
'ok': True,
'user': self.get_user_info(self.request.user),
}
@api
def stop_root(self):
"""
Lower the current request from 'root' back to normal access.
"""
if not self.request.is_admin:
raise self.forbidden()
self.request.user.record_event(self.enum.USER_EVENT_STOP_ROOT)
self.request.session['is_root'] = False
return {
'ok': True,
'user': self.get_user_info(self.request.user),
}
@api
def change_password(self):
"""
View which allows a user to change their password.
"""
if self.request.method == 'OPTIONS':
return self.request.response
if not self.request.user:
raise self.forbidden()
data = self.request.json_body
# first make sure "current" password is accurate
if not self.authenticate_user(self.request.user, data['current_password']):
return {'error': "The current/old password you provided is incorrect"}
# okay then, set new password
set_user_password(self.request.user, data['new_password'])
return {
'ok': True,
'user': self.get_user_info(self.request.user),
}
@classmethod
def defaults(cls, config):
cls._auth_defaults(config)
@classmethod
def _auth_defaults(cls, config):
# session
check_session = Service(name='check_session', path='/session')
check_session.add_view('GET', 'check_session', klass=cls)
config.add_cornice_service(check_session)
# login
login = Service(name='login', path='/login')
login.add_view('POST', 'login', klass=cls)
config.add_cornice_service(login)
# logout
logout = Service(name='logout', path='/logout')
logout.add_view('POST', 'logout', klass=cls)
config.add_cornice_service(logout)
# become root
become_root = Service(name='become_root', path='/become-root')
become_root.add_view('POST', 'become_root', klass=cls)
config.add_cornice_service(become_root)
# stop root
stop_root = Service(name='stop_root', path='/stop-root')
stop_root.add_view('POST', 'stop_root', klass=cls)
config.add_cornice_service(stop_root)
# change password
change_password = Service(name='change_password', path='/change-password')
change_password.add_view('POST', 'change_password', klass=cls)
config.add_cornice_service(change_password)
def includeme(config):
AuthenticationView.defaults(config)