Source code for wuttafarm.auth
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaFarm --Web app to integrate with and extend farmOS
# Copyright © 2026 Lance Edgar
#
# This file is part of WuttaFarm.
#
# WuttaFarm 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.
#
# WuttaFarm 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
# WuttaFarm. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Auth handler for use with farmOS
"""
from uuid import UUID
from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
from sqlalchemy import orm
from wuttjamaican.auth import AuthHandler
[docs]
class WuttaFarmAuthHandler(AuthHandler):
"""
Custom auth handler for WuttaFarm. This adds some magic around
the user login process.
It can attempt authentication against the configured farmOS
instance, auto-creating native users within the app DB as needed.
"""
[docs]
def get_role_farm_manager(self, session):
"""
Returns the special "Farm Manager" role.
"""
return self._special_role(
session, UUID("06979646-b1b9-723b-8000-b86161427f9f"), "Farm Manager"
)
[docs]
def get_role_farm_worker(self, session):
"""
Returns the special "Farm Worker" role.
"""
return self._special_role(
session, UUID("06979648-9bd6-7b61-8000-ee8818052ee8"), "Farm Worker"
)
[docs]
def get_role_farm_viewer(self, session):
"""
Returns the special "Farm Viewer" role.
"""
return self._special_role(
session, UUID("06979649-ed32-7e97-8000-113ddf0ab5f3"), "Farm Viewer"
)
[docs]
def authenticate_user(self, session, username, password):
"""
When authentication is attempted, this first will check
credentials against the app DB per normal logic. If that
succeeds, the result is no different from the typical
behavior.
If default logic fails, this will try to obtain an OAuth2
token from the farmOS site. If that succeeds, then a lookup
is done in the app DB for a matching user. If the user does
not yet exist it will be created automatically.
"""
if user := super().authenticate_user(session, username, password):
return user
if token := self.get_farmos_oauth2_token(username, password):
if user := self.get_or_make_farmos_user(session, username):
user.__dict__["farmos_oauth2_token"] = token
return user
return None
def get_farmos_oauth2_token(self, username, password):
client = self.app.get_farmos_client()
try:
return client.authorize(username=username, password=password)
except InvalidGrantError:
return None
def get_or_make_farmos_user(self, session, username):
model = self.app.model
try:
user = (
session.query(model.User).filter(model.User.username == username).one()
)
except orm.exc.NoResultFound:
pass
else:
return user if user.active else None
# nb. prevent edit for farmOS mirrored user accounts
user = self.make_user(session, username=username, prevent_edit=True)
# TODO
manager = self.get_role_farm_manager(session)
user.roles.append(manager)
return user