soliplex 0.83__tar.gz → 0.83.2__tar.gz

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 (125) hide show
  1. soliplex-0.83.2/MANIFEST.in +2 -0
  2. {soliplex-0.83/src/soliplex.egg-info → soliplex-0.83.2}/PKG-INFO +1 -1
  3. {soliplex-0.83 → soliplex-0.83.2}/pyproject.toml +1 -1
  4. soliplex-0.83.2/src/soliplex/authn.py +155 -0
  5. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/authsystem.py +103 -23
  6. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/installation.py +22 -6
  7. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/loggers.py +23 -3
  8. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/__init__.py +2 -1
  9. soliplex-0.83.2/src/soliplex/views/authn.py +535 -0
  10. soliplex-0.83.2/src/soliplex/views/templates/consent.html.mako +143 -0
  11. {soliplex-0.83 → soliplex-0.83.2/src/soliplex.egg-info}/PKG-INFO +1 -1
  12. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex.egg-info/SOURCES.txt +2 -1
  13. soliplex-0.83/MANIFEST.in +0 -1
  14. soliplex-0.83/src/soliplex/authn.py +0 -82
  15. soliplex-0.83/src/soliplex/views/authn.py +0 -177
  16. {soliplex-0.83 → soliplex-0.83.2}/LICENSE +0 -0
  17. {soliplex-0.83 → soliplex-0.83.2}/README.md +0 -0
  18. {soliplex-0.83 → soliplex-0.83.2}/example/skills/bare-bones/SKILL.md +0 -0
  19. {soliplex-0.83 → soliplex-0.83.2}/example/skills/stanzas/SKILL.md +0 -0
  20. {soliplex-0.83 → soliplex-0.83.2}/setup.cfg +0 -0
  21. {soliplex-0.83 → soliplex-0.83.2}/skills/soliplex-docs/SKILL.md +0 -0
  22. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agents.py +0 -0
  23. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agui/__init__.py +0 -0
  24. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agui/parser.py +0 -0
  25. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agui/persistence.py +0 -0
  26. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agui/schema.py +0 -0
  27. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/agui/util.py +0 -0
  28. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/__init__.py +0 -0
  29. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/env.py +0 -0
  30. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/216d48e1e2e5_soliplex_v0_53.py +0 -0
  31. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/4b63e5f3f39e_soliplex_v0_51_1.py +0 -0
  32. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/63edaa5987f6_soliplex_v0_67.py +0 -0
  33. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/a1c7d3e90b42_soliplex_v0_80.py +0 -0
  34. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/b7e2f41c9d05_soliplex_v0_82.py +0 -0
  35. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/alembic_migrations/versions/d5009d4f9874_soliplex_v0_44.py +0 -0
  36. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/authz/__init__.py +0 -0
  37. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/authz/persistence.py +0 -0
  38. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/authz/schema.py +0 -0
  39. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/capabilities/filesystem.py +0 -0
  40. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/capabilities/rag_audit.py +0 -0
  41. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/admin_users.py +0 -0
  42. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/ask.py +0 -0
  43. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/__init__.py +0 -0
  44. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/_common.py +0 -0
  45. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/admin_users.py +0 -0
  46. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/completions.py +0 -0
  47. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/databases.py +0 -0
  48. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/environment.py +0 -0
  49. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/installation.py +0 -0
  50. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/interpolation.py +0 -0
  51. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/logfire.py +0 -0
  52. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/logging.py +0 -0
  53. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/oidc.py +0 -0
  54. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/ollama.py +0 -0
  55. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/quizzes.py +0 -0
  56. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/room_authz.py +0 -0
  57. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/rooms.py +0 -0
  58. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/secrets.py +0 -0
  59. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/audit/skills.py +0 -0
  60. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/cli_util.py +0 -0
  61. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/database.py +0 -0
  62. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/main.py +0 -0
  63. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/ollama.py +0 -0
  64. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/room_authz.py +0 -0
  65. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/serve.py +0 -0
  66. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/cli/types.py +0 -0
  67. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/completions.py +0 -0
  68. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/__init__.py +0 -0
  69. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/_utils.py +0 -0
  70. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/agents.py +0 -0
  71. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/agui.py +0 -0
  72. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/completions.py +0 -0
  73. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/exceptions.py +0 -0
  74. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/interpolation.py +0 -0
  75. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/logfire.py +0 -0
  76. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/meta.py +0 -0
  77. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/middleware.py +0 -0
  78. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/quizzes.py +0 -0
  79. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/rag.py +0 -0
  80. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/rooms.py +0 -0
  81. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/routing.py +0 -0
  82. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/secrets.py +0 -0
  83. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/skills.py +0 -0
  84. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/config/tools.py +0 -0
  85. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/examples.py +0 -0
  86. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/installation.py +0 -0
  87. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/log_ingest.py +0 -0
  88. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/main.py +0 -0
  89. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/mcp_auth.py +0 -0
  90. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/mcp_client.py +0 -0
  91. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/mcp_server.py +0 -0
  92. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/models.py +0 -0
  93. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/ollama.py +0 -0
  94. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/quizzes.py +0 -0
  95. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/rag_audit.py +0 -0
  96. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/sandbox_audit.py +0 -0
  97. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/secrets.py +0 -0
  98. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/skills/bwrap_sandbox/SKILL.md +0 -0
  99. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/skills/bwrap_sandbox/__init__.py +0 -0
  100. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/titles.py +0 -0
  101. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tools/__init__.py +0 -0
  102. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tools/agui_run_feedback.py +0 -0
  103. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tools/file_uploads.py +0 -0
  104. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tools/rag.py +0 -0
  105. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tui/cli.py +0 -0
  106. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tui/main.py +0 -0
  107. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tui/rest_api.py +0 -0
  108. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/tui/serve.py +0 -0
  109. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/util.py +0 -0
  110. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/agui.py +0 -0
  111. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/authz.py +0 -0
  112. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/completions.py +0 -0
  113. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/installation.py +0 -0
  114. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/log_ingest.py +0 -0
  115. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/quizzes.py +0 -0
  116. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/room_file_uploads.py +0 -0
  117. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/rooms.py +0 -0
  118. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/sandbox_workdirs.py +0 -0
  119. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/stats.py +0 -0
  120. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/streaming.py +0 -0
  121. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex/views/thread_file_uploads.py +0 -0
  122. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex.egg-info/dependency_links.txt +0 -0
  123. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex.egg-info/entry_points.txt +0 -0
  124. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex.egg-info/requires.txt +0 -0
  125. {soliplex-0.83 → soliplex-0.83.2}/src/soliplex.egg-info/top_level.txt +0 -0
@@ -0,0 +1,2 @@
1
+ global-include SKILL.md
2
+ include src/soliplex/views/templates/*.mako
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: soliplex
3
- Version: 0.83
3
+ Version: 0.83.2
4
4
  Summary: An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface.
5
5
  Author-email: Enfold <info@enfoldsystems.net>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "soliplex"
7
- version = "0.83"
7
+ version = "0.83.2"
8
8
  description = "An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface."
9
9
  authors = [{ name = "Enfold", email = "info@enfoldsystems.net" }]
10
10
  readme = "README.md"
@@ -0,0 +1,155 @@
1
+ """Soliplex authentication support"""
2
+
3
+ import typing
4
+
5
+ import fastapi
6
+ import jwt
7
+ import starlette.config
8
+ from authlib.integrations import starlette_client
9
+ from fastapi import security
10
+
11
+ from soliplex import installation
12
+ from soliplex import loggers
13
+ from soliplex.config import authsystem as config_authsystem
14
+
15
+ UserClaims = dict[str, typing.Any]
16
+
17
+ oauth2_scheme = security.OAuth2PasswordBearer(
18
+ tokenUrl="token",
19
+ auto_error=False,
20
+ )
21
+ oauth2_predicate = fastapi.Depends(oauth2_scheme)
22
+
23
+
24
+ _oauth = None
25
+
26
+ JWT_VALIDATION_NO_TOKEN = "JWT validation failed (no token)"
27
+ JWT_VALIDATION_INVALID_TOKEN = "JWT validation failed (invalid token)"
28
+
29
+
30
+ def _get_authsystem_config(
31
+ *,
32
+ the_installation: installation.Installation,
33
+ logger: loggers.LogWrapper,
34
+ system: str,
35
+ ) -> config_authsystem.OIDCAuthSystemConfig:
36
+ """Return the indicated authsystem config
37
+
38
+ Raise a 404 if the system is in no-auth mode, or if the named
39
+ authsystem config is not found.
40
+ """
41
+ msg = loggers.AUTHN_NO_AUTH_MODE
42
+
43
+ for config in the_installation.oidc_auth_system_configs:
44
+ msg = loggers.AUTHN_UNKNOWN_AUTHSYSTEM
45
+
46
+ if config.id == system:
47
+ return config
48
+
49
+ logger.error(msg)
50
+ raise fastapi.HTTPException(
51
+ status_code=404,
52
+ detail=msg,
53
+ )
54
+
55
+
56
+ def get_oauth(
57
+ the_installation: installation.Installation,
58
+ ) -> starlette_client.OAuth:
59
+ global _oauth
60
+
61
+ if _oauth is None:
62
+ config_data = {} # Or use .env
63
+ config = starlette.config.Config(environ=config_data)
64
+ _oauth = starlette_client.OAuth(config)
65
+
66
+ for auth_system in the_installation.oidc_auth_system_configs:
67
+ auth_system_kwargs = auth_system.oauth_client_kwargs
68
+ _oauth.register(**auth_system_kwargs)
69
+
70
+ return _oauth
71
+
72
+
73
+ def authenticate(
74
+ the_installation: installation.Installation,
75
+ logger: loggers.LogWrapper,
76
+ token: str,
77
+ ) -> UserClaims:
78
+ # See #316
79
+ if the_installation.auth_disabled:
80
+ return installation.NO_AUTH_MODE_USER_TOKEN
81
+
82
+ if token is None:
83
+ logger.info(loggers.AUTHN_JWT_NOT_SET)
84
+ raise fastapi.HTTPException(
85
+ status_code=401,
86
+ detail=JWT_VALIDATION_NO_TOKEN,
87
+ )
88
+
89
+ errors = {} # accumulated; only reported if all systems miss
90
+
91
+ for auth_system in the_installation.oidc_auth_system_configs:
92
+ payload, msg = validate_access_token(
93
+ token,
94
+ auth_system,
95
+ )
96
+ if payload is not None:
97
+ return payload
98
+
99
+ errors[auth_system.id] = msg
100
+
101
+ for oidc_system_id, msg in errors.items():
102
+ logger.error(
103
+ loggers.AUTHN_JWT_INVALID,
104
+ oidc_system_id=oidc_system_id,
105
+ reason=msg,
106
+ )
107
+
108
+ raise fastapi.HTTPException(
109
+ status_code=401,
110
+ detail=JWT_VALIDATION_INVALID_TOKEN,
111
+ )
112
+
113
+
114
+ def validate_access_token(
115
+ token: str,
116
+ auth_system: config_authsystem.OIDCAuthSystemConfig,
117
+ ) -> tuple[dict | None, str | None, int | None]:
118
+ """Return '(payload, None)' if successful
119
+
120
+ Return '(None, msg)' if failed.
121
+ """
122
+ payload = msg = None
123
+ try:
124
+ payload = jwt.decode(
125
+ token,
126
+ auth_system.token_validation_pem,
127
+ algorithms=["RS256"],
128
+ issuer=auth_system.issuer,
129
+ options={
130
+ "verify_aud": False,
131
+ "require": ["exp", "iat", "iss", "sub"],
132
+ },
133
+ )
134
+ except jwt.InvalidIssuerError:
135
+ msg = loggers.AUTHN_JWT_PARSE_INVALID_ISS
136
+ except jwt.InvalidSignatureError:
137
+ msg = loggers.AUTHN_JWT_PARSE_INVALID_SIG
138
+ except jwt.MissingRequiredClaimError as exc:
139
+ msg = f"{loggers.AUTHN_JWT_PARSE_REQUIRED_CLAIM}:{exc.claim}"
140
+ except jwt.ExpiredSignatureError:
141
+ msg = loggers.AUTHN_JWT_PARSE_EXPIRED_SIG
142
+ except jwt.InvalidTokenError:
143
+ msg = loggers.AUTHN_JWT_PARSE_OTHER
144
+
145
+ if msg is not None:
146
+ return None, msg
147
+
148
+ if payload.get("azp") not in auth_system.accepted_azp_list:
149
+ return None, loggers.AUTHN_JWT_BIND_AZP
150
+
151
+ for claim, expected in auth_system.required_claims.items():
152
+ if payload.get(claim) != expected:
153
+ return None, f"{loggers.AUTHN_JWT_BIND_CLAIM}:{claim}"
154
+
155
+ return payload, None
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations # forward refs in typing decls
2
2
 
3
3
  import dataclasses
4
+ import enum
4
5
  import pathlib
5
6
  import ssl
6
7
  import typing
@@ -12,6 +13,7 @@ from . import interpolation as config_interp
12
13
  if typing.TYPE_CHECKING: # avoid an import cycle at runtime
13
14
  from . import installation as config_installation
14
15
 
16
+ _default_list_field = _utils._default_list_field
15
17
  _no_repr_no_compare_none = _utils._no_repr_no_compare_none
16
18
  _secret_whole_or_literal_field = config_interp.secret_whole_or_literal_field
17
19
 
@@ -23,6 +25,11 @@ _secret_whole_or_literal_field = config_interp.secret_whole_or_literal_field
23
25
  WELL_KNOWN_OPENID_CONFIGURATION = ".well-known/openid-configuration"
24
26
 
25
27
 
28
+ class UnlistedFrontendOriginPolicy(enum.StrEnum):
29
+ CONSENT_REQUIRED = "consent-required"
30
+ DENY_ALL = "deny-all"
31
+
32
+
26
33
  @dataclasses.dataclass(kw_only=True)
27
34
  class OIDCAuthSystemConfig:
28
35
  id: str
@@ -36,6 +43,65 @@ class OIDCAuthSystemConfig:
36
43
  # literal. 'env:' markers are not honored here.
37
44
  client_secret: str = _secret_whole_or_literal_field(default="")
38
45
  oidc_client_pem_path: pathlib.Path = None
46
+ consent_template_path: pathlib.Path = None
47
+
48
+ allowed_frontend_origins: list[str] = _default_list_field()
49
+ unlisted_frontend_origin: UnlistedFrontendOriginPolicy = (
50
+ UnlistedFrontendOriginPolicy.CONSENT_REQUIRED
51
+ )
52
+
53
+ _accepted_azp_list: list[str] | None = None
54
+
55
+ @property
56
+ def accepted_azp_list(self) -> list[str]:
57
+ """Accepted values for the 'azp' token claim."""
58
+ if self._accepted_azp_list is not None:
59
+ return self._accepted_azp_list
60
+ else:
61
+ return [self.client_id]
62
+
63
+ _issuer: str | None = None
64
+
65
+ @property
66
+ def issuer(self) -> str:
67
+ """Expected value for the 'iss' token claim."""
68
+ if self._issuer is not None:
69
+ return self._issuer
70
+ else:
71
+ return self.server_url
72
+
73
+ # Required claims: must match values
74
+ required_claims: dict[str, str] = _utils._default_dict_field()
75
+
76
+ @property
77
+ def server_metadata_url(self):
78
+ """URL for discovery of OAuth metadata"""
79
+ return f"{self.server_url}/{WELL_KNOWN_OPENID_CONFIGURATION}"
80
+
81
+ @property
82
+ def oauth_client_kwargs(self) -> dict:
83
+ """Registered kwargs for this authsystem's 'ouath' client factory"""
84
+ client_kwargs = {}
85
+
86
+ if self.scope is not None:
87
+ client_kwargs["scope"] = self.scope
88
+
89
+ if self.oidc_client_pem_path is not None:
90
+ client_kwargs["verify"] = ssl.create_default_context(
91
+ cafile=self.oidc_client_pem_path
92
+ )
93
+
94
+ client_secret = config_interp.resolve_field(self, "client_secret")
95
+
96
+ return {
97
+ "name": self.id,
98
+ "server_metadata_url": self.server_metadata_url,
99
+ "client_id": self.client_id,
100
+ "client_secret": client_secret,
101
+ "client_kwargs": client_kwargs,
102
+ # added by the auth setup
103
+ # "authorize_state": main.SESSION_SECRET_KEY,
104
+ }
39
105
 
40
106
  # Set in 'from_yaml' below
41
107
  _installation_config: config_installation.InstallationConfig = (
@@ -59,6 +125,26 @@ class OIDCAuthSystemConfig:
59
125
  config_path.parent / oidc_client_pem_path
60
126
  )
61
127
 
128
+ consent_template_path = config_dict.pop("consent_template_path", None)
129
+ if consent_template_path is not None:
130
+ config_dict["consent_template_path"] = (
131
+ config_path.parent / consent_template_path
132
+ )
133
+
134
+ ufo = config_dict.pop("unlisted_frontend_origin", None)
135
+ if ufo is not None:
136
+ config_dict["unlisted_frontend_origin"] = (
137
+ UnlistedFrontendOriginPolicy(ufo)
138
+ )
139
+
140
+ aal = config_dict.pop("accepted_azp_list", None)
141
+ if aal is not None:
142
+ config_dict["_accepted_azp_list"] = aal
143
+
144
+ iss = config_dict.pop("issuer", None)
145
+ if iss is not None:
146
+ config_dict["_issuer"] = iss
147
+
62
148
  try:
63
149
  return cls(**config_dict)
64
150
  except Exception as exc:
@@ -89,32 +175,26 @@ class OIDCAuthSystemConfig:
89
175
  # Absolutized against the config dir on load. See #1228.
90
176
  result["oidc_client_pem_path"] = str(self.oidc_client_pem_path)
91
177
 
92
- return result
178
+ if self.consent_template_path is not None:
179
+ # Absolutized against the config dir on load. See #1228.
180
+ result["consent_template_path"] = str(self.consent_template_path)
93
181
 
94
- @property
95
- def server_metadata_url(self):
96
- return f"{self.server_url}/{WELL_KNOWN_OPENID_CONFIGURATION}"
182
+ if self.allowed_frontend_origins:
183
+ result["allowed_frontend_origins"] = self.allowed_frontend_origins
97
184
 
98
- @property
99
- def oauth_client_kwargs(self) -> dict:
100
- client_kwargs = {}
185
+ if (
186
+ self.unlisted_frontend_origin
187
+ is not UnlistedFrontendOriginPolicy.CONSENT_REQUIRED
188
+ ):
189
+ result["unlisted_frontend_origin"] = self.unlisted_frontend_origin
101
190
 
102
- if self.scope is not None:
103
- client_kwargs["scope"] = self.scope
191
+ if self._accepted_azp_list is not None:
192
+ result["accepted_azp_list"] = self._accepted_azp_list
104
193
 
105
- if self.oidc_client_pem_path is not None:
106
- client_kwargs["verify"] = ssl.create_default_context(
107
- cafile=self.oidc_client_pem_path
108
- )
194
+ if self._issuer is not None:
195
+ result["issuer"] = self._issuer
109
196
 
110
- client_secret = config_interp.resolve_field(self, "client_secret")
197
+ if self.required_claims:
198
+ result["required_claims"] = self.required_claims
111
199
 
112
- return {
113
- "name": self.id,
114
- "server_metadata_url": self.server_metadata_url,
115
- "client_id": self.client_id,
116
- "client_secret": client_secret,
117
- "client_kwargs": client_kwargs,
118
- # added by the auth setup
119
- # "authorize_state": main.SESSION_SECRET_KEY,
120
- }
200
+ return result
@@ -1356,19 +1356,35 @@ class InstallationConfig:
1356
1356
  oidc_config = oidc_path / "config.yaml"
1357
1357
  config_yaml = _load_config_yaml(oidc_config)
1358
1358
 
1359
+ top_level_defaults = {}
1360
+
1359
1361
  oidc_client_pem_path = config_yaml.get("oidc_client_pem_path")
1362
+
1360
1363
  if oidc_client_pem_path is not None:
1361
- oidc_client_pem_path = oidc_path / oidc_client_pem_path
1364
+ top_level_defaults["oidc_client_pem_path"] = (
1365
+ oidc_path / oidc_client_pem_path
1366
+ )
1367
+
1368
+ consent_template_path = config_yaml.get("consent_template_path")
1369
+
1370
+ if consent_template_path is not None:
1371
+ top_level_defaults["consent_template_path"] = (
1372
+ oidc_path / consent_template_path
1373
+ )
1374
+
1375
+ afo = config_yaml.get("allowed_frontend_origins")
1376
+ if afo is not None:
1377
+ top_level_defaults["allowed_frontend_origins"] = afo
1378
+
1379
+ ufo = config_yaml.get("unlisted_frontend_origin")
1380
+ if ufo is not None:
1381
+ top_level_defaults["unlisted_frontend_origin"] = ufo
1362
1382
 
1363
1383
  for auth_system_yaml in config_yaml["auth_systems"]:
1364
- if "oidc_client_pem_path" not in auth_system_yaml:
1365
- auth_system_yaml["oidc_client_pem_path"] = (
1366
- oidc_client_pem_path
1367
- )
1368
1384
  oas_config = config_authsystem.OIDCAuthSystemConfig.from_yaml(
1369
1385
  self,
1370
1386
  oidc_config,
1371
- auth_system_yaml,
1387
+ top_level_defaults | auth_system_yaml,
1372
1388
  )
1373
1389
  oas_configs.append(oas_config)
1374
1390
 
@@ -45,11 +45,31 @@ UPLOADS_POST_ROOM_THREAD = "uploads post room thread"
45
45
  WORKDIRS_GET_ROOM_THREAD_RUN = "workdirs get room thread run"
46
46
  WORKDIRS_GET_ROOM_THREAD_RUN_FILE = "workdirs get room thread run file"
47
47
 
48
+ AUTHN_BAD_REQUEST = "authn bad request"
49
+ AUTHN_CONSENT_CANCELLED = "unlisted frontend origin: sign-in cancelled"
50
+ AUTHN_CONSENT_CONFIRMED = "unlisted frontend origin: sign-in confirmed"
51
+ AUTHN_CONSENT_INVALID = "unlisted frontend origin: invalid confirmation"
52
+ AUTHN_CONSENT_REQUESTED = "unlisted frontend origin: confirmation requested"
53
+ AUTHN_CONSENT_TEMPLATE_FAILED = "consent template failed to render"
54
+ AUTHN_NO_RETURN_TO = "no return_to stored in session"
55
+ AUTHN_UNLISTED_ORIGIN_DENIED = "unlisted frontend origin denied"
48
56
  AUTHN_LOGGER_NAME = "soliplex.authn"
49
- AUTHN_NO_AUTH_MODE = "soliplex server in no-auth mode"
50
- AUTHN_JWT_INVALID = "JWT validation failed"
51
- AUTHN_JWT_VALID = "JWT validation succeeded"
57
+ AUTHN_UNKNOWN_AUTHSYSTEM = "unknown auth system"
52
58
  AUTHN_NO_AUTH_MODE = "system in no-auth mode"
59
+
60
+ AUTHN_JWT_NOT_SET = "JWT not set"
61
+ AUTHN_JWT_VALID = "JWT validation succeeded"
62
+ AUTHN_JWT_INVALID = "JWT validation failed"
63
+
64
+ AUTHN_JWT_PARSE_INVALID_ISS = "jwt-parse-inv-iss"
65
+ AUTHN_JWT_PARSE_INVALID_SIG = "jwt-parse-inv-sig"
66
+ AUTHN_JWT_PARSE_REQUIRED_CLAIM = "jwt-parse-req-claim"
67
+ AUTHN_JWT_PARSE_EXPIRED_SIG = "jwt-parse-exp-sig"
68
+ AUTHN_JWT_PARSE_OTHER = "jwt-parse-other"
69
+
70
+ AUTHN_JWT_BIND_AZP = "jwt-bind-azp"
71
+ AUTHN_JWT_BIND_CLAIM = "jwt-bind-claim"
72
+
53
73
  AUTHN_GET_LOGIN = "get login"
54
74
  AUTHN_GET_LOGIN_SYSTEM = "get login system"
55
75
  AUTHN_GET_AUTH_SYSTEM = "get auth system"
@@ -49,10 +49,11 @@ async def get_the_user_claims(
49
49
  try:
50
50
  return authn_module.authenticate(
51
51
  the_installation=the_installation,
52
+ logger=the_unauth_logger,
52
53
  token=token,
53
54
  )
54
55
  except fastapi.HTTPException:
55
- the_unauth_logger.exception(
56
+ the_unauth_logger.error( # noqa: TRY400 `authenticate' logged details
56
57
  loggers.AUTHN_GET_USER_CLAIMS_FAILED,
57
58
  )
58
59
  raise