Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

network mode #589

Open
wants to merge 63 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
e08cc4f
network mode
mishaschwartz Sep 14, 2023
758b15f
review comment updates
mishaschwartz Oct 12, 2023
75ccf61
Merge branch 'master' into network-mode
mishaschwartz Oct 18, 2023
799ad23
network mode v2, code only
mishaschwartz Nov 1, 2023
5d22688
Merge branch 'master' into network-mode
mishaschwartz Nov 1, 2023
4c79d8a
add cron job
mishaschwartz Nov 2, 2023
2c2fd34
documentation updates
mishaschwartz Nov 2, 2023
35090da
docstring updates
mishaschwartz Nov 2, 2023
79714b2
general cleanup and schema updates
mishaschwartz Nov 6, 2023
18ccc4c
deal with pyjwt's python versions support
mishaschwartz Nov 6, 2023
7ec1c9b
deal with jwcrypto's python versions support
mishaschwartz Nov 6, 2023
50aa4e5
fix accidental infinite recursion
mishaschwartz Nov 6, 2023
3e266c7
clean up planning comments and fix route loading with network mode is…
mishaschwartz Nov 6, 2023
38410de
remove unused imports
mishaschwartz Nov 7, 2023
4a6ed0d
ok fine, we'll try to support end-of-life pythons
mishaschwartz Nov 7, 2023
d690c41
ok let's try this version again
mishaschwartz Nov 7, 2023
5f4087d
checks updates
mishaschwartz Nov 7, 2023
18ee9dd
fix css
mishaschwartz Nov 7, 2023
a3aa357
review suggestions
mishaschwartz Dec 18, 2023
1bf5584
style fixes
mishaschwartz Dec 18, 2023
3e94161
route name fix
mishaschwartz Dec 18, 2023
5ec26f9
Merge branch 'master' into network-mode
mishaschwartz Jan 19, 2024
c632d08
added documentation about skipping a check and added a cache to regex…
mishaschwartz Jan 19, 2024
50a86b7
make lru_cache compatible with older pythons
mishaschwartz Jan 19, 2024
c32501b
make all arguments hashable for lru_cache functions
mishaschwartz Jan 19, 2024
521af09
spacing fix
mishaschwartz Jan 26, 2024
1029847
Merge branch 'master' into network-mode
mishaschwartz Feb 16, 2024
9be6121
add timeout to requests (CWE-400)
mishaschwartz Feb 16, 2024
aa1e9e4
initial tests and test setup
mishaschwartz Feb 25, 2024
ef2ebd1
Merge branch 'master' into network-mode
mishaschwartz Apr 1, 2024
b337464
bug fixes and testing helper methods
mishaschwartz Apr 2, 2024
f5de462
some more tests and bugfixes
mishaschwartz Apr 5, 2024
72d2bb3
ensure network mode is configured properly at startup
mishaschwartz Apr 9, 2024
93d177f
add option to create private key at startup
mishaschwartz Apr 10, 2024
fedc5ed
finish up tests for network views
mishaschwartz Apr 16, 2024
7f6a8a0
most of the tests for network nodes
mishaschwartz Apr 17, 2024
249f4a1
use check functions instead of plain assert
mishaschwartz Apr 19, 2024
df90ba2
finish remote user tests and fix issue where multiple remote users co…
mishaschwartz Apr 22, 2024
9de7fee
don't allow explicit assign to anonymous users so that we don't get m…
mishaschwartz Apr 22, 2024
e56504e
add cli and constants tests
mishaschwartz Apr 23, 2024
aa8f43d
add ui tests
mishaschwartz Apr 24, 2024
d4b01b1
style fixes
mishaschwartz Apr 24, 2024
cd9c048
fix dependency versions
mishaschwartz Apr 24, 2024
8107ebb
fix requirements file: old range not parsed properly by pip?
mishaschwartz Apr 24, 2024
18bc104
test fixes
mishaschwartz Apr 24, 2024
add132e
support for python 3.5
mishaschwartz Apr 24, 2024
a140783
add option to create private key through makefile
mishaschwartz Apr 24, 2024
34a1dd0
style fixes
mishaschwartz Apr 24, 2024
7002b2d
style fixes
mishaschwartz Apr 24, 2024
336e5c3
try to ignore some test code
mishaschwartz Apr 24, 2024
704f2bc
give up and just do something different
mishaschwartz Apr 24, 2024
5f587de
try to fix gitleaks again
mishaschwartz Apr 24, 2024
76944c4
fix test exception when python < 3.7
mishaschwartz Apr 25, 2024
8c03aab
don't mess with required test names
mishaschwartz Apr 25, 2024
a34a060
ooops
mishaschwartz Apr 25, 2024
ae9caf6
run the tests that I want. Env variable is overridden by command line…
mishaschwartz Apr 25, 2024
37b9f62
try this again with the proper name since it adds a suffix I guess
mishaschwartz Apr 25, 2024
c8385c9
Merge branch 'master' into network-mode
mishaschwartz Apr 26, 2024
bd2da76
remove code to support python versions < 3.8
mishaschwartz Apr 26, 2024
c5057cb
remove unused import
mishaschwartz Apr 26, 2024
9a839b7
another one
mishaschwartz Apr 26, 2024
64894e3
Merge branch 'master' into network-mode
mishaschwartz Jun 11, 2024
3e903fd
Merge branch 'master' into network-mode
mishaschwartz Sep 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ Changes

Features / Changes
~~~~~~~~~~~~~~~~~~~~~
* n/a
* Introduce "Network Mode" which allows other Magpie instances to act as external authentication providers using JSON
Web Tokens (JWT). This allows users registered across multiple Magpie instances in a network to more easily gain
access to the resources within the network, without requiring the duplication of user credentials across the network.

Bug Fixes
~~~~~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 4 additions & 0 deletions config/magpie.ini
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ pyramid.includes =
magpie.port = 2001
magpie.url = http://localhost:2001

# Enable network mode which allows different instances of Magpie to authenticate users for each other.
# magpie.network_mode = true
# magpie.default_token_expiry = 86400
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing instance name setting


# magpie.config_path =

# --- cookie definition --- (defaults below if omitted)
Expand Down
81 changes: 81 additions & 0 deletions docs/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,84 @@ Furthermore, as described in the `procedure`_, :envvar:`MAGPIE_USER_REGISTRATION
specify whether administrator approval is required or not. This additional step is purely up to the developers and
server managers that use `Magpie` to decide if they desire more control over which individuals can join and access
their services.

.. _Network Mode:

Network Mode
------------

If the :envvar:`MAGPIE_NETWORK_MODE` is enabled, an additional external authentication provider is added to Magpie which
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use `Magpie` anywhere it is mentioned in the docs.

allows networked instances of Magpie to authenticate users for each other.

Users can then log in to any Magpie instance where they have an account and request a personal network token in the form
of a `_JSON Web Token <https://datatracker.ietf.org/doc/html/rfc7519>`_ which can be used to authenticate this user on
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad underscore at start

any Magpie instance in the network.

Managing the Network
~~~~~~~~~~~~~~~~~~~~

In order for Magpie instances to authenticate each other's users, each instance must be made aware of the existence of
the others so that it knows where to send authentication verification requests.

In order to register another Magpie instance as part of the same network, an admin user can create a
:term:`Network Node` with a request to ``POST /network-nodes``. The parameters given to that request include a ``name``
and a ``url``. The ``name`` is a the name of that other Magpie instance in the network and should correspond to the
same value as the :envvar:`MAGPIE_INSTANCE_NAME` value set by the other Magpie instance. The ``url`` is a the root URL
of the other Magpie instance.

Once a :term:`Network Node` is registered, Magpie can use that other instance to authenticate users as long as the other
instance also has :envvar:`MAGPIE_NETWORK_MODE` enabled.

Managing Personal JWTs
~~~~~~~~~~~~~~~~~~~~~~

A :term:`User` can request a new network token with a request to the ``PATCH /token`` route. This route takes one
optional parameter ``expires`` which is an integer indicating how long (in seconds) until that token expires, the
default expiry for this token is :envvar:`MAGPIE_DEFAULT_TOKEN_EXPIRY`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is usually not up to the user to decide the expiry time for security reasons.
Each Magpie instance enforces a token expiration, and it should be up to their authentication mechanism to refresh its token as needed.


Every time a :term:`User` makes a request to the ``PATCH /token`` route a new token is generated for them. This
effectively cancels all previously created tokens for that user. If a user wishes to cancel all tokens, they can provide
an ``expires`` value of ``0`` when making the request.

Authentication
~~~~~~~~~~~~~~

Once a :term:`User` gets a personal network token, they can use that token to authenticate with any Magpie instance in
the same network. When a user makes a request, they should set the ``provider_name`` parameter to the value of
:envvar:`MAGPIE_NETWORK_PROVIDER` and provide the network token in the Authorization header in the following format:

.. code-block:: http

Authorization: Bearer <network_token>

When using the :ref:`Magpie Adapter <utilities_adapter>`, the token can also be passed as a parameter to the request,
where the parameter name set by :envvar:`MAGPIE_NETWORK_TOKEN_NAME` and the value is the personal network token.

Authorization
~~~~~~~~~~~~~

Managing authorization for :term:`Users` who authenticate using personal network tokens is complicated by the fact that
a :term:`User` is not required to have a full account on both Magpie instances in order to using this authentication
mechanism. This means that a :term:`User` may be logged in as a node-specific "anonymous" user.

When another Magpie instance is registered as a :term:`Network Node`, a few additional entities are created:

#. a group used to manage the permissions of all users who authenticate using the new :term:`Network Node`.
* this group's name will be the :envvar:`MAGPIE_NETWORK_NAME_PREFIX` followed by the :term:`Network Node` name
#. a group used to manage the permissions of all users who authenticate using *any* other instance in the network
* this group's name will be the :envvar:`MAGPIE_NETWORK_GROUP_NAME`
* this group will only be created once, when the first :term:`Network Node` is registered
#. an anonymous user that belongs to the two groups that were just created.
* this user name will be the :envvar:`MAGPIE_NETWORK_NAME_PREFIX` followed by the :term:`Network Node` name
Comment on lines +524 to +525
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a valid user name (eg: pavics) was created on a node that had MAGPIE_NETWORK_NAME_PREFIX=pavics, another Magpie instance (eg: daccs) using network mode will not be able to distinguish that user pavics from that network node's anonymous user.

Or maybe this is just an impression? But it looks like it could be possible to inject the "right values" to cause side effects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the MAGPIE_NETWORK_NAME_PREFIX=pavics then you shouldn't be able to create any new users whose name starts with pavics. Even so, the anonymous user for the daccs node would be pavicsdaccs (the prefix followed by the network name).

Now, if you already had a user in the system named pavicsdaccs that could be a bit confusing but they should still be able to differentiate them because one would belong to the pavicsdaccs group and the original user would not.

If this is easier though, I'm happy to split these users out into different tables as you suggest (#589 (review)) if that will keep things cleaner.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that would make it easier conceptually. The API endpoints however would still need to consider these conditions to allow user names or not. I think you mostly already did it with the regexes, but tests must be done to make sure it works everywhere (trying to purposely cause conflict, edge cases and such).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I have some tests on the way but they're still in progress :)


Here is an example to illustrate this point:

* There are 3 Magpie instances in the network named A, B, and C
* There is a :term:`User` named ``"toto"`` registered on instance A
* There is no :term:`User` named ``"toto"`` who belongs to the ``"anonymous_network_A"`` group registered on instance B
* There is a :term:`User` named ``"toto"`` who belongs to the ``"anonymous_network_A"`` group registered on instance C
* Instance A is registered as a :term:`Network Node` on instances B and C
* when ``"toto"`` gets a personal network token from instance A and uses it to log in on instance B they log in as the
the temporary ``"anonymous_network_A"`` user.
* when ``"toto"`` gets a personal network token from instance A and uses it to log in on instance C they log in as the
``"toto"`` user on instance C.
Comment on lines +536 to +537
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if user toto already existed on instance C but is not the same person toto from A? Is it actually toto user on instance C, or some kind of toto_network_A user on instance C?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are logged in at the toto user who belongs to the toto_network_A group

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Will need to investigate more how conflicting user names are handled. Basically, we shouldn't assume that a name would necessarily be exactly the same on other Magpie instances. Preferably, alternative/remote Magpie instances should allow to "link" users of different names, similarly to how you can have 2 distinct user names on distinct social media platforms, but link accounts together. Retrieving the "resolved user_name" from another Magpie in the network could use similar approach to the userinfo of OAuth.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I see what you mean. I'm feeling strongly from this review that we should be handling these remote users by storing them separately in another db table. That would resolve a lot of the issues that you raise here about conflicting names as well

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remote users are now stored in a different table: network_remote_users

77 changes: 77 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,83 @@ remain available as described at the start of the :ref:`Configuration` section.
Name of the :term:`Provider` used for login. This represents the identifier that is set to define how to
differentiate between a local sign-in procedure and a dispatched one some known :ref:`authn_providers`.

Network Mode Settings
~~~~~~~~~~~~~~~~~~~~~

The following configuration parameters are related to Magpie's "Network Mode" which allows networked instances of Magpie
to authenticate users for each other. All variables defined in this section are only used if
:envvar:`MAGPIE_NETWORK_MODE` is enabled.

.. envvar:: MAGPIE_NETWORK_MODE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use MAGPIE_NETWORK_ENABLED to be consistent with other feature-enabling options.


[:class:`bool`]
(Default: ``False``)

.. versionadded:: 3.37
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 versions ahead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I assumed that this change would be its own version and the current unreleased changes would be made into version 3.36 before then. But I don't know how you decide on your releases for Magpie so I can change this if that's not the case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's fine. It's mostly a reminder for adjusting is as necessary when the feature is completed.


Enable "Network Mode" which enables all functionality to authenticate users using other Magpie instances as
external authentication providers.

.. envvar:: MAGPIE_INSTANCE_NAME
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix with MAGPIE_NETWORK since it is relevant for that feature.


[:class:`str`]

.. versionadded:: 3.37

The name of this Magpie instance in the network. This variable is used to determine if an authentication token was
issued by this instance of Magpie, or another instance in the network.

This variable is required if :envvar:`MAGPIE_NETWORK_MODE` is ``True``.

.. envvar:: MAGPIE_DEFAULT_TOKEN_EXPIRY
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix with MAGPIE_NETWORK since it is relevant only for that feature, and there are already other token expiration concepts used elsewhere.


[:class:`int`]
(Default: ``86400``)

.. versionadded:: 3.37

The default expiry time (in seconds) for an authentication token issued for the purpose of network authentication.

.. envvar:: MAGPIE_NETWORK_TOKEN_NAME

[|constant|_]
(Value: ``"magpie_token"``)

.. versionadded:: 3.37

The name of the request parameter key whose value is the authentication token issued for the purpose of network
authentication.

.. envvar:: MAGPIE_NETWORK_PROVIDER

[|constant|_]
(Value: ``"magpie_network"``)

.. versionadded:: 3.37

The name of the external provider that authenticates users using other Magpie instances as external authentication
providers.

.. envvar:: MAGPIE_NETWORK_NAME_PREFIX

[|constant|_]
(Value: ``"anonymous_network_"``)

.. versionadded:: 3.37

A prefix added to the anonymous network user and network group names. These names are constructed by prepending the
remote Magpie instance name with this prefix. For example, a Magpie instance named ``"example123"`` will have a
corresponding user and group named ``"anonymous_network_example123"``.

.. envvar:: MAGPIE_NETWORK_GROUP_NAME

[|constant|_]
(Value: ``"magpie_network"``)

.. versionadded:: 3.37

The name of the group created to manage permissions for all users authenticated using Magpie instances as external
authentication providers.

.. _config_phoenix:

Expand Down
10 changes: 10 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ Glossary
:py:data:`magpie.constants.MAGPIE_ANONYMOUS_USER`. Otherwise, it is whoever the
:term:`Authentication` mechanism identifies with token extracted from request :term:`Cookies`.

Network Node
A reference to an instance of the Magpie software within a network of Magpie instances. Each Magpie instance
within the network is registered in the database as a row in the ``network_nodes`` table. Each node is
represented by a name that is unique across all nodes in the network, and a url that is used to send http
requests to that specific node.

Network Token
A unique random string that can be used to authenticate a user as part of the :ref:`Network Mode` authentication
procedure.

OpenAPI
OAS
The |OpenAPI-spec|_ (`OAS`) defines a standard, programming language-agnostic interface description for
Expand Down
9 changes: 8 additions & 1 deletion magpie/adapter/magpieowssecurity.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,11 +269,18 @@ def update_request_cookies(self, request):
"""
settings = get_settings(request)
token_name = get_constant("MAGPIE_COOKIE_NAME", settings_container=settings)
network_mode = get_constant("MAGPIE_NETWORK_MODE", settings_container=settings,
settings_name="magpie.network_mode")
headers = dict(request.headers)
network_token_name = get_constant("MAGPIE_NETWORK_TOKEN_NAME", settings_container=settings)
if network_mode and "Authorization" not in headers and network_token_name in request.params:
headers["Authorization"] = "Bearer {}".format(request.params[network_token_name])
Comment on lines +275 to +276
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the Authorization: Bearer {token} header directly?

It seems the endpoint for providers is used also in the login code, but here a separate request parameter is used instead of reusing provider_name designed for this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use the Authorization: Bearer {token} header directly?

I think the idea here was to provide an option for cases when a user wants to access a resource and doesn't have the ability to set the request headers. For example, if they're using a tool that just asks for a URL.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can understand the logic behind it. If really necessary for tools with limited capabilities, that can be permitted.
However, consider that this is less secure than headers. Path/Query are too often dumped in logs or directly exposed.
If you can use headers, it is preferable because it will obfuscate the tokens slightly more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I completely agree. The short lifespan of these tokens is supposed to mitigate this risk a bit.

request.params["provider_name"] = request.params.get("provider_name",
get_constant("MAGPIE_NETWORK_PROVIDER", settings))
if "Authorization" in request.headers and token_name not in request.cookies:
magpie_prov = request.params.get("provider_name", get_constant("MAGPIE_DEFAULT_PROVIDER", settings))
magpie_path = ProviderSigninAPI.path.format(provider_name=magpie_prov)
magpie_auth = "{}{}".format(self.magpie_url, magpie_path)
headers = dict(request.headers)
headers.update({"Homepage-Route": "/session", "Accept": CONTENT_TYPE_JSON})
session_resp = requests.get(magpie_auth, headers=headers, verify=self.twitcher_ssl_verify)
if session_resp.status_code != HTTPOk.code:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
Add Network_Tokens Table

Revision ID: 2cfe144538e8
Revises: 5e5acc33adce
Create Date: 2023-08-25 13:36:16.930374
"""
import uuid

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy_utils import URLType

# Revision identifiers, used by Alembic.
# pylint: disable=C0103,invalid-name # revision control variables not uppercase
revision = "2cfe144538e8"
down_revision = "5e5acc33adce"
branch_labels = None
depends_on = None

Session = sessionmaker()


def upgrade():
op.create_table("network_tokens",
sa.Column("token", UUID(as_uuid=True),
primary_key=True, default=uuid.uuid4, unique=True),
sa.Column("user_id", sa.Integer,
sa.ForeignKey("users.id", onupdate="CASCADE", ondelete="CASCADE"), unique=True)
)
op.create_table("network_nodes",
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
sa.Column("name", sa.Unicode(128), nullable=False, unique=True),
sa.Column("url", URLType(), nullable=False)
)
op.add_column("users", sa.Column("network_node_id", sa.Integer,
sa.ForeignKey("network_nodes.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=True))
op.drop_constraint("uq_users_user_name", "users")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot remove this constraint without a lot of validation that all the code still works.
User names are used for login and for querying the API. They must be unique to find the right ones directly. Nothing was designed to handle potentially returning multiple matches.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I tried to search through to catch all the cases for the current searches and enforce that the associated network node be null. Another pair of eyes would be great.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, whenever <user>.user_name is accessed in the code, that <user> reference must be resolved beforehand based on what was passed as input from the API, UI, CLI or configuration startup.

This is an important one:

def get_user(request, user_name_or_token=None, user_status=None):

There are also some considerations to be taken for

class UserPending(Base):

and
class TemporaryToken(BaseModel, Base):

which generates a somewhat related definition to a future user, but that is only really "linked" via user_name until the relevant registration/approval process is completed, since the actual user_id is not defined yet.

This code never checks for potential multi-results returned from user_name matches because it always assumed the unique constraint.

Similarly, all webhooks events for interactions with Cowbird work using user_name, since this is what the API paths use.

op.create_unique_constraint("uq_users_user_name_network_node_id", "users", ["user_name", "network_node_id"])
op.drop_constraint("uq_users_email", "users")
op.create_unique_constraint("uq_users_email_network_node_id", "users", ["email", "network_node_id"])


def downgrade():
op.drop_constraint("uq_users_user_name_network_node_id", "users")
op.create_unique_constraint("uq_users_user_name", "users", ["user_name"])
op.drop_constraint("uq_users_email_network_node_id", "users")
op.create_unique_constraint("uq_users_email", "users", ["email"])
op.drop_table("network_tokens")
op.drop_table("network_nodes")
op.drop_column("users", "network_node_id")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to consider dropping the duplicate user-names that have a network_node_id IS NULL to ensure that the recreation of the unique username constraint does not cause an error if duplicates exist.

18 changes: 14 additions & 4 deletions magpie/api/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def verify_param( # noqa: E126 # pylint: disable=R0913,too-many-arguments
is_equal=False, # type: bool
is_type=False, # type: bool
matches=False, # type: bool
not_matches=False, # type: bool
): # type: (...) -> None # noqa: E123,E126
# pylint: disable=R0912,R0914
"""
Expand Down Expand Up @@ -123,12 +124,13 @@ def verify_param( # noqa: E126 # pylint: disable=R0913,too-many-arguments
:param is_equal: test that :paramref:`param` equals :paramref:`param_compare` value
:param is_type: test that :paramref:`param` is of same type as specified by :paramref:`param_compare` type
:param matches: test that :paramref:`param` matches the regex specified by :paramref:`param_compare` value
:param not_matches: test that :paramref:`param` doesn't match the regex specified by :paramref:`param_compare` value
:raises HTTPError: if tests fail, specified exception is raised (default: :class:`HTTPBadRequest`)
:raises HTTPInternalServerError: for evaluation error
:return: nothing if all tests passed
"""
content = {} if content is None else content
needs_compare = is_type or is_in or not_in or is_equal or not_equal or matches
needs_compare = is_type or is_in or not_in or is_equal or not_equal or matches or not_matches
needs_iterable = is_in or not_in

# precondition evaluation of input parameters
Expand Down Expand Up @@ -159,9 +161,11 @@ def verify_param( # noqa: E126 # pylint: disable=R0913,too-many-arguments
raise TypeError("'is_type' is not a 'bool'")
if not isinstance(matches, bool):
raise TypeError("'matches' is not a 'bool'")
if not isinstance(not_matches, bool):
raise TypeError("'not_matches' is not a 'bool'")
# error if none of the flags specified
if not any([not_none, not_empty, not_in, not_equal,
is_none, is_empty, is_in, is_equal, is_true, is_false, is_type, matches]):
is_none, is_empty, is_in, is_equal, is_true, is_false, is_type, matches, not_matches]):
raise ValueError("no comparison flag specified for verification")
if param_compare is None and needs_compare:
raise TypeError("'param_compare' cannot be 'None' with specified test flags")
Expand All @@ -178,11 +182,11 @@ def verify_param( # noqa: E126 # pylint: disable=R0913,too-many-arguments
is_str_cmp = isinstance(param, six.string_types)
ok_str_cmp = isinstance(param_compare, six.string_types)
eq_typ_cmp = type(param) is type(param_compare)
is_pattern = matches and isinstance(param_compare, Pattern)
is_pattern = (matches or not_matches) and isinstance(param_compare, Pattern)
if is_type and not (is_str_typ or is_cmp_typ):
LOGGER.debug("[param: %s] invalid type compare with [param_compare: %s]", type(param), param_compare)
raise TypeError("'param_compare' cannot be of non-type with specified verification flags")
if matches and not isinstance(param_compare, (six.string_types, Pattern)):
if (matches or not_matches) and not isinstance(param_compare, (six.string_types, Pattern)):
LOGGER.debug("[param_compare: %s] invalid type is not a regex string or pattern", type(param_compare))
raise TypeError("'param_compare' for matching verification must be a string or compile regex pattern")
if not is_type and not ((is_str_cmp and ok_str_cmp) or (not is_str_cmp and eq_typ_cmp) or is_pattern):
Expand Down Expand Up @@ -252,6 +256,12 @@ def verify_param( # noqa: E126 # pylint: disable=R0913,too-many-arguments
param_compare_regex = re.compile(param_compare, re.I | re.X)
fail_conditions.update({"matches": bool(re.match(param_compare_regex, param))})
fail_verify = fail_verify or not fail_conditions["matches"]
if not_matches:
param_compare_regex = param_compare
if isinstance(param_compare, six.string_types):
param_compare_regex = re.compile(param_compare, re.I | re.X)
fail_conditions.update({"not_matches": not re.match(param_compare_regex, param)})
fail_verify = fail_verify or not fail_conditions["not_matches"]
if fail_verify:
content = apply_param_content(content, param, param_compare, param_name, with_param, param_content,
needs_compare, needs_iterable, is_type, fail_conditions)
Expand Down
4 changes: 4 additions & 0 deletions magpie/api/login/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from magpie.constants import get_constant
from magpie.utils import get_logger

LOGGER = get_logger(__name__)
Expand All @@ -11,4 +12,7 @@ def includeme(config):
config.add_route(**s.service_api_route_info(s.SigninAPI))
config.add_route(**s.service_api_route_info(s.ProvidersAPI))
config.add_route(**s.service_api_route_info(s.ProviderSigninAPI))
if get_constant("MAGPIE_NETWORK_MODE", settings_name="magpie.network_mode"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pass the config to access settings.

config.add_route(**s.service_api_route_info(s.TokenAPI))
config.add_route(**s.service_api_route_info(s.TokenValidateAPI))
config.scan()
Loading
Loading