invenio-oauth2server 5.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. invenio_oauth2server/__init__.py +119 -0
  2. invenio_oauth2server/admin.py +81 -0
  3. invenio_oauth2server/alembic/12a88921ada2_create_oauth2server_tables.py +96 -0
  4. invenio_oauth2server/alembic/3d7f57a61d67_change_expires_at_type.py +36 -0
  5. invenio_oauth2server/alembic/4e57407b8e4a_add_on_delete_cascade.py +122 -0
  6. invenio_oauth2server/alembic/aa546f2a8d2f_create_oauth2server_branch.py +21 -0
  7. invenio_oauth2server/cli.py +96 -0
  8. invenio_oauth2server/config.py +78 -0
  9. invenio_oauth2server/decorators.py +72 -0
  10. invenio_oauth2server/errors.py +95 -0
  11. invenio_oauth2server/ext.py +350 -0
  12. invenio_oauth2server/forms.py +189 -0
  13. invenio_oauth2server/models.py +428 -0
  14. invenio_oauth2server/provider.py +156 -0
  15. invenio_oauth2server/proxies.py +12 -0
  16. invenio_oauth2server/scopes.py +16 -0
  17. invenio_oauth2server/templates/invenio_oauth2server/authorize.html +63 -0
  18. invenio_oauth2server/templates/invenio_oauth2server/base.html +13 -0
  19. invenio_oauth2server/templates/invenio_oauth2server/errors.html +33 -0
  20. invenio_oauth2server/templates/invenio_oauth2server/settings/_macros.html +30 -0
  21. invenio_oauth2server/templates/invenio_oauth2server/settings/base.html +13 -0
  22. invenio_oauth2server/templates/invenio_oauth2server/settings/client_new.html +27 -0
  23. invenio_oauth2server/templates/invenio_oauth2server/settings/client_view.html +96 -0
  24. invenio_oauth2server/templates/invenio_oauth2server/settings/helpers.html +58 -0
  25. invenio_oauth2server/templates/invenio_oauth2server/settings/index.html +84 -0
  26. invenio_oauth2server/templates/invenio_oauth2server/settings/token_new.html +28 -0
  27. invenio_oauth2server/templates/invenio_oauth2server/settings/token_permission_view.html +49 -0
  28. invenio_oauth2server/templates/invenio_oauth2server/settings/token_view.html +50 -0
  29. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/authorize.html +69 -0
  30. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/base.html +13 -0
  31. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/errors.html +28 -0
  32. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/_macros.html +78 -0
  33. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/base.html +13 -0
  34. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/client_new.html +29 -0
  35. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/client_view.html +121 -0
  36. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/helpers.html +118 -0
  37. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/index.html +95 -0
  38. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/token_new.html +30 -0
  39. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/token_permission_view.html +49 -0
  40. invenio_oauth2server/templates/semantic-ui/invenio_oauth2server/settings/token_view.html +60 -0
  41. invenio_oauth2server/theme/semantic/form_styling.py +46 -0
  42. invenio_oauth2server/translations/ar/LC_MESSAGES/messages.po +492 -0
  43. invenio_oauth2server/translations/bg/LC_MESSAGES/messages.po +467 -0
  44. invenio_oauth2server/translations/ca/LC_MESSAGES/messages.po +468 -0
  45. invenio_oauth2server/translations/cs/LC_MESSAGES/messages.po +495 -0
  46. invenio_oauth2server/translations/da/LC_MESSAGES/messages.po +468 -0
  47. invenio_oauth2server/translations/de/LC_MESSAGES/messages.po +506 -0
  48. invenio_oauth2server/translations/el/LC_MESSAGES/messages.po +468 -0
  49. invenio_oauth2server/translations/es/LC_MESSAGES/messages.po +498 -0
  50. invenio_oauth2server/translations/et/LC_MESSAGES/messages.po +495 -0
  51. invenio_oauth2server/translations/fa/LC_MESSAGES/messages.po +467 -0
  52. invenio_oauth2server/translations/fi/LC_MESSAGES/messages.po +496 -0
  53. invenio_oauth2server/translations/fr/LC_MESSAGES/messages.po +498 -0
  54. invenio_oauth2server/translations/hr/LC_MESSAGES/messages.po +467 -0
  55. invenio_oauth2server/translations/hu/LC_MESSAGES/messages.po +497 -0
  56. invenio_oauth2server/translations/it/LC_MESSAGES/messages.po +493 -0
  57. invenio_oauth2server/translations/ja/LC_MESSAGES/messages.po +467 -0
  58. invenio_oauth2server/translations/ka/LC_MESSAGES/messages.po +467 -0
  59. invenio_oauth2server/translations/ko/LC_MESSAGES/messages.po +467 -0
  60. invenio_oauth2server/translations/lt/LC_MESSAGES/messages.po +467 -0
  61. invenio_oauth2server/translations/messages.pot +463 -0
  62. invenio_oauth2server/translations/no/LC_MESSAGES/messages.po +467 -0
  63. invenio_oauth2server/translations/pl/LC_MESSAGES/messages.po +468 -0
  64. invenio_oauth2server/translations/pt/LC_MESSAGES/messages.po +467 -0
  65. invenio_oauth2server/translations/ro/LC_MESSAGES/messages.po +495 -0
  66. invenio_oauth2server/translations/ru/LC_MESSAGES/messages.po +468 -0
  67. invenio_oauth2server/translations/sk/LC_MESSAGES/messages.po +488 -0
  68. invenio_oauth2server/translations/sv/LC_MESSAGES/messages.po +501 -0
  69. invenio_oauth2server/translations/tr/LC_MESSAGES/messages.po +498 -0
  70. invenio_oauth2server/translations/uk/LC_MESSAGES/messages.po +468 -0
  71. invenio_oauth2server/translations/zh_CN/LC_MESSAGES/messages.po +469 -0
  72. invenio_oauth2server/translations/zh_TW/LC_MESSAGES/messages.po +467 -0
  73. invenio_oauth2server/utils.py +65 -0
  74. invenio_oauth2server/validators.py +60 -0
  75. invenio_oauth2server/views/__init__.py +13 -0
  76. invenio_oauth2server/views/server.py +152 -0
  77. invenio_oauth2server/views/settings.py +268 -0
  78. invenio_oauth2server-5.0.1.dist-info/METADATA +74 -0
  79. invenio_oauth2server-5.0.1.dist-info/RECORD +83 -0
  80. invenio_oauth2server-5.0.1.dist-info/WHEEL +4 -0
  81. invenio_oauth2server-5.0.1.dist-info/entry_points.txt +35 -0
  82. invenio_oauth2server-5.0.1.dist-info/licenses/AUTHORS.rst +25 -0
  83. invenio_oauth2server-5.0.1.dist-info/licenses/LICENSE +20 -0
@@ -0,0 +1,119 @@
1
+ # SPDX-FileCopyrightText: 2015-2024 CERN.
2
+ # SPDX-FileCopyrightText: 2023-2026 Graz University of Technology.
3
+ # SPDX-FileCopyrightText: 2025 KTH Royal Institute of Technology.
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ """Invenio module that implements OAuth 2 server.
7
+
8
+ Protecting your REST API with authentication
9
+ --------------------------------------------
10
+
11
+ If you want to have your REST API endpoints protected using OAuth you
12
+ should register their blueprint inside the API app
13
+ (InvenioOAuth2ServerREST) which by default includes a ``before_request`` hook.
14
+ This hook will, if there is an OAuth token, verify it and set the current user
15
+ accordingly. It is important to highlight that this configuration allows
16
+ either authenticated clients or anonymous clients.
17
+
18
+ In case you need to allow access to a resource only for authenticated clients,
19
+ you should use the `require_api_auth
20
+ <api.html#invenio_oauth2server.decorators.require_api_auth>`__
21
+ decorator which requires OAuth2 login:
22
+
23
+ .. code-block:: python
24
+
25
+ @app.route('/api/resource', methods=['GET'])
26
+ @require_api_auth()
27
+ def index():
28
+ return 'Protected resource'
29
+
30
+ However, protecting your resources only with authentication is not
31
+ recommended. Instead, you should add an extra layer using always
32
+ `scopes <api.html#invenio_oauth2server.models.Scope>`__. This is because,
33
+ basically, any client that owns a token has control over every user resource.
34
+ Therefore, using scopes gives a fine-grain control. Here an example using
35
+ the default ``email_scope``:
36
+
37
+ .. code-block:: python
38
+
39
+ from invenio_oauth2server.scopes import email_scope
40
+ from flask_login import current_user
41
+
42
+ @app.route('/api/email', methods=['GET'])
43
+ @require_api_auth()
44
+ @require_oauth_scopes(email_scope.id_)
45
+ def index():
46
+ return current_user.email
47
+
48
+ Delegating rights via scopes
49
+ ----------------------------
50
+
51
+ As mentioned before, the recommended way to protect your endpoints is to use
52
+ fine-grain control with scopes. Invenio-OAuth2Server offers the possibility to
53
+ create new ones:
54
+
55
+ .. code-block:: python
56
+
57
+ from invenio_oauth2server.models import Scope
58
+
59
+ homepage_read = Scope('homepage:read',
60
+ help_text='Access to the homepage',
61
+ group='test')
62
+
63
+ Next, you should add them to ``setup.py`` entrypoints so they get initialized
64
+ at start up:
65
+
66
+ .. code-block:: python
67
+
68
+ setup(
69
+ ...
70
+ entry_points={
71
+ 'invenio_oauth2server.scopes': [
72
+ 'homepage_read = path.to.scopes.file:homepage_read',
73
+ ]
74
+ }
75
+ ...
76
+ )
77
+
78
+ And then, they can be used in your application:
79
+
80
+ .. code-block:: python
81
+
82
+ from path.to.scopes.file import homepage_read
83
+
84
+ @app.route('/', methods=['GET'])
85
+ @require_api_auth()
86
+ @require_oauth_scopes(homepage_read.id_)
87
+ def index():
88
+ return 'Front page content.'
89
+
90
+ So, finally, with this example, we would allow any authenticated client with
91
+ rights to use the ``homepage_scope`` to read the homepage but, prevent from
92
+ reading the email if they do not have rights for using the ``email_scope``.
93
+
94
+ Access control
95
+ --------------
96
+
97
+ It is important to remember that the usage of authentication and scopes is not
98
+ enough in most of the cases so access control need to be configured as well.
99
+ For more information about access control in Invenio you can visit
100
+ `Invenio-Access
101
+ <http://invenio-access.readthedocs.io/en/latest/>`__ documentation.
102
+
103
+
104
+ """
105
+
106
+ from .decorators import require_api_auth, require_oauth_scopes
107
+ from .ext import InvenioOAuth2Server, InvenioOAuth2ServerREST
108
+ from .proxies import current_oauth2server
109
+
110
+ __version__ = "5.0.1"
111
+
112
+ __all__ = (
113
+ "__version__",
114
+ "InvenioOAuth2Server",
115
+ "InvenioOAuth2ServerREST",
116
+ "require_api_auth",
117
+ "require_oauth_scopes",
118
+ "current_oauth2server",
119
+ )
@@ -0,0 +1,81 @@
1
+ # SPDX-FileCopyrightText: 2016-2018 CERN.
2
+ # SPDX-FileCopyrightText: 2024 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Admin views for managing access to actions."""
6
+
7
+ from flask_admin.contrib.sqla import ModelView
8
+ from invenio_db import db
9
+
10
+ from .models import Client, Token
11
+
12
+
13
+ def _(x):
14
+ """Identity."""
15
+ return x
16
+
17
+
18
+ class ClientView(ModelView):
19
+ """View for managing access to actions by users."""
20
+
21
+ can_delete = True
22
+ can_create = False
23
+ can_view_details = True
24
+ column_display_all_relations = True
25
+
26
+ list_all = (
27
+ "name",
28
+ "description",
29
+ "website",
30
+ "user_id",
31
+ "client_id",
32
+ )
33
+
34
+ column_list = column_searchable_list = column_sortable_list = (
35
+ column_details_list
36
+ ) = list_all
37
+
38
+ column_list = list_all
39
+ column_default_sort = ("client_id", True)
40
+
41
+
42
+ class TokenView(ModelView):
43
+ """View for managing access to actions by users with given role."""
44
+
45
+ can_delete = True
46
+ can_create = False
47
+ can_view_details = True
48
+ list_all = (
49
+ "id",
50
+ "client_id",
51
+ "user_id",
52
+ "token_type",
53
+ "expires",
54
+ )
55
+ column_list = column_searchable_list = column_sortable_list = (
56
+ column_details_list
57
+ ) = list_all
58
+
59
+
60
+ oauth2server_clients_adminview = {
61
+ "view_class": ClientView,
62
+ "args": [Client, db.session],
63
+ "kwargs": {
64
+ "name": _("OAuth Applications"),
65
+ "category": _("User Management"),
66
+ },
67
+ }
68
+
69
+ oauth2server_tokens_adminview = {
70
+ "view_class": TokenView,
71
+ "args": [Token, db.session],
72
+ "kwargs": {
73
+ "name": _("OAuth Application Tokens"),
74
+ "category": _("User Management"),
75
+ },
76
+ }
77
+
78
+ __all__ = (
79
+ "oauth2server_clients_adminview",
80
+ "oauth2server_tokens_adminview",
81
+ )
@@ -0,0 +1,96 @@
1
+ # SPDX-FileCopyrightText: 2016-2018 CERN.
2
+ # SPDX-FileCopyrightText: 2024 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Create oauth2server tables."""
6
+
7
+ import sqlalchemy as sa
8
+ import sqlalchemy_utils
9
+ from alembic import op
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = "12a88921ada2"
13
+ down_revision = "aa546f2a8d2f"
14
+ branch_labels = ()
15
+ depends_on = "9848d0149abd"
16
+
17
+
18
+ def upgrade():
19
+ """Upgrade database."""
20
+ op.create_table(
21
+ "oauth2server_client",
22
+ sa.Column("name", sa.String(length=40), nullable=True),
23
+ sa.Column("description", sa.Text(), nullable=True),
24
+ sa.Column("website", sqlalchemy_utils.types.url.URLType(), nullable=True),
25
+ sa.Column("user_id", sa.Integer(), nullable=True),
26
+ sa.Column("client_id", sa.String(length=255), nullable=False),
27
+ sa.Column("client_secret", sa.String(length=255), nullable=False),
28
+ sa.Column("is_confidential", sa.Boolean(name="is_confidential"), nullable=True),
29
+ sa.Column("is_internal", sa.Boolean(name="is_internal"), nullable=True),
30
+ sa.Column("_redirect_uris", sa.Text(), nullable=True),
31
+ sa.Column("_default_scopes", sa.Text(), nullable=True),
32
+ sa.ForeignKeyConstraint(
33
+ ["user_id"],
34
+ ["accounts_user.id"],
35
+ ),
36
+ sa.PrimaryKeyConstraint("client_id"),
37
+ )
38
+ op.create_index(
39
+ op.f("ix_oauth2server_client_client_secret"),
40
+ "oauth2server_client",
41
+ ["client_secret"],
42
+ unique=True,
43
+ )
44
+ op.create_table(
45
+ "oauth2server_token",
46
+ sa.Column("id", sa.Integer(), nullable=False),
47
+ sa.Column("client_id", sa.String(length=255), nullable=False),
48
+ sa.Column("user_id", sa.Integer(), nullable=True),
49
+ sa.Column("token_type", sa.String(length=255), nullable=True),
50
+ sa.Column(
51
+ "access_token", sqlalchemy_utils.StringEncryptedType(), nullable=True
52
+ ),
53
+ sa.Column(
54
+ "refresh_token", sqlalchemy_utils.StringEncryptedType(), nullable=True
55
+ ),
56
+ sa.Column("expires", sa.DateTime(), nullable=True),
57
+ sa.Column("_scopes", sa.Text(), nullable=True),
58
+ sa.Column("is_personal", sa.Boolean(name="is_personal"), nullable=True),
59
+ sa.Column("is_internal", sa.Boolean(name="is_internal"), nullable=True),
60
+ sa.ForeignKeyConstraint(
61
+ ["client_id"],
62
+ ["oauth2server_client.client_id"],
63
+ ),
64
+ sa.ForeignKeyConstraint(
65
+ ["user_id"],
66
+ ["accounts_user.id"],
67
+ ),
68
+ sa.PrimaryKeyConstraint("id"),
69
+ )
70
+ op.create_index(
71
+ "ix_oauth2server_token_access_token",
72
+ "oauth2server_token",
73
+ ["access_token"],
74
+ unique=True,
75
+ mysql_length=255,
76
+ )
77
+ op.create_index(
78
+ "ix_oauth2server_token_refresh_token",
79
+ "oauth2server_token",
80
+ ["refresh_token"],
81
+ unique=True,
82
+ mysql_length=255,
83
+ )
84
+
85
+
86
+ def downgrade():
87
+ """Downgrade database."""
88
+ op.drop_index(
89
+ "ix_oauth2server_token_refresh_token", table_name="oauth2server_token"
90
+ )
91
+ op.drop_index("ix_oauth2server_token_access_token", table_name="oauth2server_token")
92
+ op.drop_table("oauth2server_token")
93
+ op.drop_index(
94
+ op.f("ix_oauth2server_client_client_secret"), table_name="oauth2server_client"
95
+ )
96
+ op.drop_table("oauth2server_client")
@@ -0,0 +1,36 @@
1
+ # SPDX-FileCopyrightText: 2017-2018 CERN.
2
+ # SPDX-FileCopyrightText: 2026 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Change expires_at type to utc aware datetime."""
6
+
7
+ from alembic import op
8
+ from invenio_db import db
9
+
10
+ # revision identifiers, used by Alembic.
11
+ revision = "3d7f57a61d67"
12
+ down_revision = "4e57407b8e4a"
13
+ branch_labels = ()
14
+ depends_on = None
15
+
16
+
17
+ def upgrade():
18
+ """Upgrade database."""
19
+ op.alter_column(
20
+ "oauth2server_token",
21
+ "expires",
22
+ type_=db.UTCDateTime(),
23
+ existing_type=db.DateTime(),
24
+ existing_nullable=True,
25
+ )
26
+
27
+
28
+ def downgrade():
29
+ """Downgrade database."""
30
+ op.alter_column(
31
+ "oauth2server_token",
32
+ "expires",
33
+ type_=db.DateTime(),
34
+ existing_type=db.UTCDateTime(),
35
+ existing_nullable=True,
36
+ )
@@ -0,0 +1,122 @@
1
+ # SPDX-FileCopyrightText: 2017-2018 CERN.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Add on delete cascade."""
5
+
6
+ from alembic import op
7
+
8
+ # revision identifiers, used by Alembic.
9
+ revision = "4e57407b8e4a"
10
+ down_revision = "12a88921ada2"
11
+ branch_labels = ()
12
+ depends_on = None
13
+
14
+
15
+ def upgrade():
16
+ """Upgrade database."""
17
+ op.drop_constraint(
18
+ "fk_oauth2server_client_user_id_accounts_user",
19
+ "oauth2server_client",
20
+ type_="foreignkey",
21
+ )
22
+ op.create_foreign_key(
23
+ op.f("fk_oauth2server_client_user_id_accounts_user"),
24
+ "oauth2server_client",
25
+ "accounts_user",
26
+ ["user_id"],
27
+ ["id"],
28
+ ondelete="CASCADE",
29
+ )
30
+ op.create_index(
31
+ op.f("ix_oauth2server_client_user_id"),
32
+ "oauth2server_client",
33
+ ["user_id"],
34
+ unique=False,
35
+ )
36
+ op.drop_constraint(
37
+ "fk_oauth2server_token_user_id_accounts_user",
38
+ "oauth2server_token",
39
+ type_="foreignkey",
40
+ )
41
+ op.create_foreign_key(
42
+ op.f("fk_oauth2server_token_user_id_accounts_user"),
43
+ "oauth2server_token",
44
+ "accounts_user",
45
+ ["user_id"],
46
+ ["id"],
47
+ ondelete="CASCADE",
48
+ )
49
+ op.create_index(
50
+ op.f("ix_oauth2server_token_user_id"),
51
+ "oauth2server_token",
52
+ ["user_id"],
53
+ unique=False,
54
+ )
55
+ op.drop_constraint(
56
+ "fk_oauth2server_token_client_id_oauth2server_client",
57
+ "oauth2server_token",
58
+ type_="foreignkey",
59
+ )
60
+ op.create_foreign_key(
61
+ op.f("fk_oauth2server_token_client_id_oauth2server_client"),
62
+ "oauth2server_token",
63
+ "oauth2server_client",
64
+ ["client_id"],
65
+ ["client_id"],
66
+ ondelete="CASCADE",
67
+ )
68
+ op.create_index(
69
+ op.f("ix_oauth2server_token_client_id"),
70
+ "oauth2server_token",
71
+ ["client_id"],
72
+ unique=False,
73
+ )
74
+
75
+
76
+ def downgrade():
77
+ """Downgrade database."""
78
+ op.drop_constraint(
79
+ op.f("fk_oauth2server_token_user_id_accounts_user"),
80
+ "oauth2server_token",
81
+ type_="foreignkey",
82
+ )
83
+ op.drop_index(
84
+ op.f("ix_oauth2server_token_user_id"), table_name="oauth2server_token"
85
+ )
86
+ op.create_foreign_key(
87
+ "fk_oauth2server_token_user_id_accounts_user",
88
+ "oauth2server_token",
89
+ "accounts_user",
90
+ ["user_id"],
91
+ ["id"],
92
+ )
93
+ op.drop_constraint(
94
+ op.f("fk_oauth2server_token_client_id_oauth2server_client"),
95
+ "oauth2server_token",
96
+ type_="foreignkey",
97
+ )
98
+ op.drop_index(
99
+ op.f("ix_oauth2server_token_client_id"), table_name="oauth2server_token"
100
+ )
101
+ op.create_foreign_key(
102
+ "fk_oauth2server_token_client_id_oauth2server_client",
103
+ "oauth2server_token",
104
+ "oauth2server_client",
105
+ ["client_id"],
106
+ ["client_id"],
107
+ )
108
+ op.drop_constraint(
109
+ op.f("fk_oauth2server_client_user_id_accounts_user"),
110
+ "oauth2server_client",
111
+ type_="foreignkey",
112
+ )
113
+ op.drop_index(
114
+ op.f("ix_oauth2server_client_user_id"), table_name="oauth2server_client"
115
+ )
116
+ op.create_foreign_key(
117
+ "fk_oauth2server_client_user_id_accounts_user",
118
+ "oauth2server_client",
119
+ "accounts_user",
120
+ ["user_id"],
121
+ ["id"],
122
+ )
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: 2016-2018 CERN.
2
+ # SPDX-FileCopyrightText: 2023 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """Create oauth2server branch."""
6
+
7
+ # revision identifiers, used by Alembic.
8
+ revision = "aa546f2a8d2f"
9
+ down_revision = None
10
+ branch_labels = ("invenio_oauth2server",)
11
+ depends_on = "dbdbc1b19cf2"
12
+
13
+
14
+ def upgrade():
15
+ """Upgrade database."""
16
+ pass
17
+
18
+
19
+ def downgrade():
20
+ """Downgrade database."""
21
+ pass
@@ -0,0 +1,96 @@
1
+ # SPDX-FileCopyrightText: 2018 CERN.
2
+ # SPDX-FileCopyrightText: 2024 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """CLI commands for managing the OAuth server."""
6
+
7
+ from functools import wraps
8
+
9
+ import click
10
+ from flask.cli import with_appcontext
11
+ from invenio_accounts.models import User
12
+ from invenio_db import db
13
+ from werkzeug.local import LocalProxy
14
+
15
+ from .models import Client, Token
16
+ from .validators import validate_scopes
17
+
18
+
19
+ def lazy_result(f):
20
+ """Decorate function to return LazyProxy."""
21
+
22
+ @wraps(f)
23
+ def decorated(ctx, param, value):
24
+ return LocalProxy(lambda: f(ctx, param, value))
25
+
26
+ return decorated
27
+
28
+
29
+ @lazy_result
30
+ def process_user(ctx, param, value):
31
+ """Return a user if exists."""
32
+ if value:
33
+ if value.isdigit():
34
+ user = db.session.get(User, str(value))
35
+ else:
36
+ user = User.query.filter(User.email == value).one_or_none()
37
+ return user
38
+
39
+
40
+ @lazy_result
41
+ def process_scopes(ctx, param, values):
42
+ """Return a user if exists."""
43
+ validate_scopes(values)
44
+ return values
45
+
46
+
47
+ @click.group()
48
+ def tokens():
49
+ """OAuth2 server token commands."""
50
+
51
+
52
+ @tokens.command("create")
53
+ @click.option("-n", "--name", required=True)
54
+ @click.option(
55
+ "-u", "--user", required=True, callback=process_user, help="User ID or email."
56
+ )
57
+ @click.option("-s", "--scope", "scopes", multiple=True, callback=process_scopes)
58
+ @click.option("-i", "--internal", is_flag=True)
59
+ @with_appcontext
60
+ def tokens_create(name, user, scopes, internal):
61
+ """Create a personal OAuth token."""
62
+ token = Token.create_personal(name, user.id, scopes=scopes, is_internal=internal)
63
+ db.session.commit()
64
+ click.secho(token.access_token, fg="blue")
65
+
66
+
67
+ @tokens.command("delete")
68
+ @click.option("-n", "--name")
69
+ @click.option("-u", "--user", callback=process_user, help="User ID or email.")
70
+ @click.option("--token", "read_access_token", is_flag=True)
71
+ @click.option("--force", is_flag=True)
72
+ @with_appcontext
73
+ def tokens_delete(name=None, user=None, read_access_token=None, force=False):
74
+ """Delete a personal OAuth token."""
75
+ if not (name or user) and not read_access_token:
76
+ click.get_current_context().fail(
77
+ 'You have to pass either a "name" and "user" or the "token"'
78
+ )
79
+ if name and user:
80
+ client = Client.query.filter(
81
+ Client.user_id == user.id, Client.name == name, Client.is_internal.is_(True)
82
+ ).one()
83
+ token = Token.query.filter(
84
+ Token.user_id == user.id,
85
+ Token.is_personal.is_(True),
86
+ Token.client_id == client.client_id,
87
+ ).one()
88
+ elif read_access_token:
89
+ access_token = click.prompt("Token", hide_input=True)
90
+ token = Token.query.filter(Token.access_token == access_token).one()
91
+ else:
92
+ click.get_current_context().fail("No token was found with provided")
93
+ if force or click.confirm("Are you sure you want to delete the token?"):
94
+ db.session.delete(token)
95
+ db.session.commit()
96
+ click.secho('Token "{}" deleted.'.format(token.access_token), fg="yellow")
@@ -0,0 +1,78 @@
1
+ # SPDX-FileCopyrightText: 2015-2018 CERN.
2
+ # SPDX-FileCopyrightText: 2023 Graz University of Technology.
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """OAuth2Server configuration variables."""
6
+
7
+ OAUTH2_CACHE_TYPE = "redis"
8
+ """Type of cache to use for storing the temporary grant token."""
9
+
10
+ OAUTH2_PROVIDER_ERROR_ENDPOINT = "invenio_oauth2server.errors"
11
+ """Error view endpoint."""
12
+
13
+ OAUTH2SERVER_CLIENT_ID_SALT_LEN = 40
14
+ """Length of client id."""
15
+
16
+ OAUTH2SERVER_CLIENT_SECRET_SALT_LEN = 60
17
+ """Length of the client secret."""
18
+
19
+ OAUTH2SERVER_TOKEN_PERSONAL_SALT_LEN = 60
20
+ """Length of the personal access token."""
21
+
22
+ OAUTH2SERVER_ALLOWED_GRANT_TYPES = {
23
+ "authorization_code",
24
+ "client_credentials",
25
+ "refresh_token",
26
+ }
27
+ """A set of allowed grant types.
28
+
29
+ The allowed values are ``authorization_code``, ``password``,
30
+ ``client_credentials``, ``refresh_token``). By default password is disabled,
31
+ as it requires the client application to gain access to the username and
32
+ password of the resource owner.
33
+ """
34
+
35
+ OAUTH2SERVER_ALLOWED_RESPONSE_TYPES = {
36
+ "code",
37
+ "token",
38
+ }
39
+ """A set of allowed response types.
40
+
41
+ The allowed values are ``code`` and ``token``.
42
+
43
+ - ``code`` is used for authorization_code grant types
44
+ - ``token`` is used for implicit grant types
45
+ """
46
+
47
+ OAUTH2SERVER_ALLOWED_URLENCODE_CHARACTERS = "=&;:%+~,*@!()/?"
48
+ """A string of special characters that should be valid inside a query string.
49
+
50
+ .. seealso::
51
+
52
+ See :py:func:`monkeypatch_oauthlib_urlencode_chars
53
+ <invenio_oauth2server.ext.InvenioOAuth2ServerREST.monkeypatch_oauthlib_urlencode_chars>`
54
+ for a full explanation.
55
+ """
56
+
57
+ OAUTH2SERVER_JWT_AUTH_HEADER = "Authorization"
58
+ """Header for the JWT.
59
+
60
+ .. note::
61
+
62
+ Authorization: Bearer xxx
63
+ """
64
+
65
+ OAUTH2SERVER_JWT_AUTH_HEADER_TYPE = "Bearer"
66
+ """Header Authorization type.
67
+
68
+ .. note::
69
+
70
+ By default the authorization type is ``Bearer`` as recommented by
71
+ `JWT <https://jwt.io>`_
72
+ """
73
+
74
+ OAUTH2SERVER_JWT_VERIFICATION_FACTORY = "invenio_oauth2server.utils:" "jwt_verify_token"
75
+ """Import path of factory used to verify JWT.
76
+
77
+ The ``request.headers`` should be passed as parameter.
78
+ """