[FIX] Grammar and spelling

This commit is contained in:
Peter Pfeufer
2023-12-17 20:07:14 +01:00
parent 8aeb061635
commit 6e3219fd1b
52 changed files with 422 additions and 424 deletions

View File

@@ -1,16 +1,16 @@
# Integrating Services
One of the primary roles of Alliance Auth is integrating with external services in order to authenticate and manage users. This is achieved through the use of service modules.
One of the primary roles of Alliance Auth is integrating with external services to authenticate and manage users. This is achieved through the use of service modules.
## The Service Module
Each service module is its own self contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module.
Each service module is its own self-contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module.
Normally service modules live in `services.modules` though they may also be installed as external packages and installed via `pip` if you wish. A module is installed by including it in the `INSTALLED_APPS` setting.
### Service Module Structure
Typically a service will contain 5 key components:
Typically, a service will contain 5 key components:
- [The Hook](#the-hook)
- [The Service Manager](#the-service-manager)
@@ -36,11 +36,11 @@ The architecture looks something like this:
Module --▶ Dependency/Import
```
While this is the typical structure of the existing services modules, there is no enforcement of this structure and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally though, using the common structure improves the maintainability for other developers.
While this is the typical structure of the existing services modules, there is no enforcement of this structure, and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally, using the common structure improves the maintainability for other developers.
### The Hook
In order to integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example:
To integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example:
```python
@hooks.register('services_hook')
@@ -61,7 +61,7 @@ class ExampleService(ServicesHook):
def __init__(self):
ServicesHook.__init__(self)
self.urlpatterns = urlpatterns
self.service_url = 'http://exampleservice.example.com'
self.service_url = 'https://exampleservice.example.com'
"""
Overload base methods here to implement functionality
@@ -70,9 +70,9 @@ class ExampleService(ServicesHook):
### The ServiceHook class
The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions in order to trigger some action in the service.
The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions to trigger some action in the service.
You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All of the functions are optional, so its up to you to define what you need.
You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All the functions are optional, so its up to you to define what you need.
Instance Variables:
@@ -103,7 +103,7 @@ Internal name of the module, should be unique amongst modules.
#### self.urlpatterns
You should define all of your service URLs internally, usually in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns.
You should usually define all of your service URLs internally, in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns.
```python
from . import urls
@@ -122,13 +122,13 @@ This is provided as a courtesy and defines the default template to be used with
#### title
This is a property which provides a user friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalization. If this is the case you should override this method and return a string.
This is a property which provides a user-friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalization. If this is the case, you should override this method and return a string.
#### delete_user
`def delete_user(self, user, notify_user=False):`
Delete the users service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled.
Delete the user's service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled.
The function should return a boolean, `True` if successfully disabled, `False` otherwise.
@@ -136,7 +136,7 @@ The function should return a boolean, `True` if successfully disabled, `False` o
`def validate_user(self, user):`
Validate the users service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object.
Validate the user's service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object.
An implementation will probably look like the following:
@@ -155,7 +155,7 @@ This function will be called periodically on all users to validate that the give
`def sync_nickname(self, user):`
Very optional. As of writing only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronized with the service.
Very optional. As of writing, only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronized with the service.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
@@ -165,7 +165,7 @@ If this function is defined, an admin action will be registered on the Django Us
Updates the nickname for a list of users. The `users` parameter must be a list of Django User objects.
If this method is defined, the admin action for updating service related nicknames for users will call this bulk method instead of sync_nickname. This gives you more control over how mass updates are executed, e.g. ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
If this method is defined, the admin action for updating service related nicknames for users will call this bulk method instead of sync_nickname. This gives you more control over how mass updates are executed, e.g., ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
This is an optional method.
@@ -173,12 +173,12 @@ This is an optional method.
`def update_groups(self, user):`
Update the users group membership. The `user` parameter should be a Django User object.
When this is called the service should determine the groups the user is a member of and synchronize the group membership with the external service. If you service does not support groups then you are not required to define this.
Update the user's group membership. The `user` parameter should be a Django User object.
When this is called, the service should determine the groups the user is a member of and synchronize the group membership with the external service. If your service does not support groups, then you are not required to define this.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
This action is usually called via a signal when a users group membership changes (joins or leaves a group).
This action is usually called via a signal when a user's group membership changes (joins or leaves a group).
#### update_groups_bulk
@@ -186,7 +186,7 @@ This action is usually called via a signal when a users group membership changes
Updates the group memberships for a list of users. The `users` parameter must be a list of Django User objects.
If this method is defined, the admin action for updating service related groups for users will call this bulk method instead of update_groups. This gives you more control over how mass updates are executed, e.g. ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
If this method is defined, the admin action for updating service related groups for users will call this bulk method instead of update_groups. This gives you more control over how mass updates are executed, e.g., ensuring updates do not run in parallel to avoid causing rate limit violations from an external API.
This is an optional method.
@@ -204,7 +204,7 @@ I'm really not sure when this is called, it may have been a hold over from befor
Is this service active for the given user? The `user` parameter should be a Django User object.
Usually you wont need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the users state.
Usually you won't need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the user's state.
#### show_service_ctrl
@@ -212,7 +212,7 @@ Usually you wont need to override this as it calls `service_enabled_members` or
Should the service be shown for the given `user` with the given `state`? The `user` parameter should be a Django User object, and the `state` parameter should be a valid state from `authentication.states`.
Usually you wont need to override this function.
Usually you won't need to override this function.
For more information see the [render_service_ctrl](#render_service_ctrl) section.
@@ -222,7 +222,7 @@ For more information see the [render_service_ctrl](#render_service_ctrl) section
Render the services control row. This will be called for all active services when a user visits the `/services/` page and [show_service_ctrl](#show_service_ctrl) returns `True` for the given user.
It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the users username for this service, column #3 is the services URL, and column #4 is the action buttons.
It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the user's username for this service, column #3 is the services URL, and column #4 is the action buttons.
You may either define your own service template or use the default one provided. The default can be used like this example:
@@ -248,61 +248,61 @@ def render_services_ctrl(self, request):
}, request=request)
```
the `Urls` class defines the available URL names for the 4 actions available in the default template:
the `Urls` class defines the available URL names for the four actions available in the default template:
- Activate (create service account)
- Deactivate (delete service account)
- Activate (create a service account)
- Deactivate (delete a service account)
- Reset Password (random password)
- Set Password (custom password)
If you don't define one or all of these variable the button for the undefined URLs will not be displayed.
If you don't define one or all of these variables, the button for the undefined URLs will not be displayed.
Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though may be confusing to users.
Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though it may be confusing to users.
### Menu Item Hook
If you services needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class.
If your service needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class.
For more information see the [Menu Hooks](menu-hooks.md) page.
For more information, see the [Menu Hooks](menu-hooks.md) page.
### The Service Manager
The service manager is what interacts with the external service. Ideally it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g. Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute.
The service manager is what interacts with the external service. Ideally, it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g., Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute.
The reason for maintaining this separation is that managers may be reused from other sources and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment others may reuse the managers that we write. It can also significantly ease the unit testing of services.
The reason for maintaining this separation is that managers may be reused from other sources, and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment, others may reuse the managers that we write. It can also significantly ease the unit testing of services.
### The Views
As mentioned at the start of this page, service modules are fully fledged Django apps. This means you're free to do whatever you wish with your views.
Typically most traditional username/password services define four views.
Typically, most traditional username/password services define four views.
- Create Account
- Delete Account
- Reset Password
- Set Password
These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example OAuth functionality.
These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example, OAuth functionality.
### The Tasks
The tasks component is the glue that holds all of the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, validating the existence of a users account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here.
The tasks component is the glue that holds all the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, and validating the existence of a user's account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here.
### The Models
Its very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually.
It's very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps, you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually.
If you create models you should create the migrations that go along with these inside of your module/app.
If you create models, you should create the migrations that go along with them inside your module/app.
## Examples
There is a bare bones example service included in `services.modules.example`, you may like to use this as the base for your new service.
There is a bare-bones example service included in `services.modules.example`, you may like to use this as the base for your new service.
You should have a look through some of the other service modules before you get started to get an idea of the general structure of one. A lot of them aren't perfect so don't feel like you have to rigidly follow the structure of the existing services if you think its sub-optimal or doesn't suit the external service you're integrating.
You should have a look through some of the other service modules before you get started to get an idea of the general structure. A lot of them aren't perfect, so don't feel like you have to rigidly follow the structure of the existing services if you think its suboptimal or doesn't suit the external service you're integrating.
## Testing
You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service so you should mock the appropriate components to prevent this behavior.
You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service, so you should mock the appropriate components to prevent this behavior.
```{eval-rst}
.. autoclass:: allianceauth.services.hooks.ServicesHook

View File

@@ -1,8 +1,8 @@
# Menu Hooks
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this, you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
To register a MenuItemHook class you would do the following:
To register a MenuItemHook class, you would do the following:
```python
@hooks.register('menu_item_hook')
@@ -16,7 +16,7 @@ The `MenuItemHook` class specifies some parameters/instance variables required f
### text
The text shown as menu item, e.g. usually the name of the app.
The text shown as menu item, e.g., usually the name of the app.
### classes
@@ -28,7 +28,7 @@ The name of the Django URL to use
### order
An integer which specifies the order of the menu item, lowest to highest. Community apps are free ot use an oder above `1000`. Numbers below are served for Auth.
An integer which specifies the order of the menu item, lowest to highest. Community apps are free ot use an oder above `1000`. The numbers below are reserved for Auth.
### navactive
@@ -38,12 +38,12 @@ A list of views or namespaces the link should be highlighted on. See [django-nav
`count` is an integer shown next to the menu item as badge when `count` is not `None`.
This is a great feature to signal the user, that he has some open issues to take care of within an app. For example Auth uses this feature to show the specific number of open group request to the current user.
This is a great feature to signal the user that he has some open issues to take care of within an app. For example, Auth uses this feature to show the specific number of open group request to the current user.
:::{hint}
Here is how to stay consistent with the Auth design philosophy for using this feature:
1. Use it to display open items that the current user can close by himself only. Do not use it for items, that the user has no control over.
1. Use it to display open items that the current user can close by himself only. Do not use it for items that the user has no control over.
2. If there are currently no open items, do not show a badge at all.
:::
To use it set count the `render()` function of your subclass in accordance to the current user. Here is an example:

View File

@@ -2,9 +2,9 @@
## Base functionality
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.
The URL hooks allow you to dynamically specify URL patterns from your plugin app or service. To achieve this, you should subclass or instantiate the `services.hooks.UrlHook` class and then register the URL patterns with the hook.
To register a UrlHook class you would do the following:
To register a UrlHook class, you would do the following:
```python
@hooks.register('url_hook')
@@ -14,12 +14,12 @@ def register_urls():
### Public views
In addition is it possible to make views public. Normally, all views are automatically decorated with the `main_character_required` decorator. That decorator ensures a user needs to be logged in and have a main before he can access that view. This feature protects against a community app sneaking in a public view without the administrator knowing about it.
In addition, is it possible to make views public. Normally, all views are automatically decorated with the `main_character_required` decorator. That decorator ensures a user needs to be logged in and have a main before he can access that view. This feature protects against a community app sneaking in a public view without the administrator knowing about it.
An app can opt-out of this feature by adding a list of views to be excluded when registering the URLs. See the `excluded_views` parameter for details.
An app can opt out of this feature by adding a list of views to be excluded when registering the URLs. See the `excluded_views` parameter for details.
:::{note}
Note that for a public view to work, administrators need to also explicitly allow apps to have public views in their AA installation, by adding the apps label to ``APPS_WITH_PUBLIC_VIEWS`` setting.
Note that for a public view to work, administrators need to also explicitly allow apps to have public views in their AA installation, by adding the app label to ``APPS_WITH_PUBLIC_VIEWS`` setting.
:::
## Examples
@@ -42,7 +42,7 @@ urlpatterns = [
]
```
Subsequently it would implement the UrlHook in a dedicated `auth_hooks.py` file like so:
Subsequently, it would implement the UrlHook in a dedicated `auth_hooks.py` file like so:
```python
from alliance_auth import hooks