Skip to content

Commit dfc06e9

Browse files
authored
RHCLOUD-37820: user lookup endpoint (#1569)
* configure logging for rbac.internal packages, added logs to internal middleware utils * start new endpoint for getting user data with username or email. support getting username from bop if only email present * add whitespace * honor include_permissions in request to bop * always query bop for user * query all the required data and return it * use underscore for email * updated error message * ran linter and fixed errors * fixed lint error for membership check, moved Sentry error to new errors file * added method docs and fixed formatting * removed usage of deprecated function * catch exception when querying for principal, and use correct options in request to bop * fixed invalid assertions, added happy path test for get_user_data * add warning if bop user doesnt have required data, added tests for invalid requests * split up internal view set tests into multiple classes and a base class * log warning when user in bop but not rbac, add more tests to cover the rest of get_user_data * updates after running linter * abstract out the path for user lookup tests * change endpoint name to user_lookup * catch error if identity header not included on request * use exact match when querying for username, and update error message * handle case where no principal exists in rbac, and multiple exist in rbac * updated api spec * remove todo * revert unneeded change * add functionality to disable auth for internal apis * linter fixes * dont expose no_auth_paths as env var because that could be a security risk * remove uuid and platform and admind efault * add default for role description * utilize util methods * linter fixes * log exceptions * fix existing tests * linter fix * test for public tenant * add new test cases, use assertCountEqual * linter fixes * cover case where tenant does not exist * Add test to cover exception * linter fixes * remove no auth for user lookup * pass user tenant to get_principal method * remove blank line * update test to account for exception being caught * add optional to get_principal and add a test for this case
1 parent 93e32be commit dfc06e9

File tree

11 files changed

+941
-40
lines changed

11 files changed

+941
-40
lines changed

Diff for: rbac/internal/errors.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#
2+
# Copyright 2025 Red Hat, Inc.
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU Affero General Public License as
6+
# published by the Free Software Foundation, either version 3 of the
7+
# License, or (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU Affero General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Affero General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
#
17+
18+
"""Public errors related to internal views/endpoints."""
19+
20+
21+
class UserNotFoundError(Exception):
22+
"""Raised when a user cannot be found via proxy service (bop)."""
23+
24+
pass
25+
26+
27+
class SentryDiagnosticError(Exception):
28+
"""Raise this to create an event in Sentry."""
29+
30+
pass

Diff for: rbac/internal/middleware.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def process_request(self, request):
5656
else:
5757
try:
5858
_, json_rh_auth = extract_header(request, self.header)
59-
except (JSONDecodeError, binascii.Error):
59+
except (JSONDecodeError, binascii.Error, KeyError):
6060
logger.exception("Invalid X-RH-Identity header.")
6161
return HttpResponseForbidden()
6262
user = build_internal_user(request, json_rh_auth)

Diff for: rbac/internal/specs/openapi.json

+129
Original file line numberDiff line numberDiff line change
@@ -2015,6 +2015,124 @@
20152015
}
20162016
}
20172017
},
2018+
"/api/utils/user_lookup/": {
2019+
"get":{
2020+
"summary": "Get user info",
2021+
"description": "Query for a user's groups, roles, and permissions based on their username or email. Only one of the params is required.",
2022+
"operationId": "userLookup",
2023+
"parameters": [
2024+
{
2025+
"name": "username",
2026+
"in": "query",
2027+
"description": "Username of the desired user to query for. If both username and email are provided, username is used and email is ignored.",
2028+
"required": true,
2029+
"schema":{
2030+
"type": "string"
2031+
}
2032+
},
2033+
{
2034+
"name": "email",
2035+
"in": "query",
2036+
"description": "Email address of the desired user to query for. If both username and email are provided, username is used and email is ignored.",
2037+
"required": true,
2038+
"schema":{
2039+
"type": "string"
2040+
}
2041+
}
2042+
],
2043+
"responses": {
2044+
"200": {
2045+
"description": "Result set of user groups, roles, and permissions.",
2046+
"content": {
2047+
"application/json": {
2048+
"schema": {
2049+
"type": "object",
2050+
"properties": {
2051+
"username": { "type": "string", "example": "fake_user" },
2052+
"email_address": { "type": "string", "example": "[email protected]" },
2053+
"groups":{
2054+
"type": "array",
2055+
"items":{
2056+
"type": "object",
2057+
"properties": {
2058+
"name": { "type": "string", "example": "Example group"},
2059+
"description": { "type": "string", "example": "A group for some users" },
2060+
"uuid": { "type": "string", "format":"uuid", "example": "1c4da003-569d-433f-8159-fd77e6984de1" },
2061+
"platform_default": { "type": "boolean", "example": true },
2062+
"admin_default": { "type": "boolean", "example": false },
2063+
"roles":{
2064+
"type": "array",
2065+
"items": {
2066+
"type": "object",
2067+
"properties": {
2068+
"name": { "type": "string", "example": "Example Role" },
2069+
"display_name": { "type": "string", "example": "Example Role Display" },
2070+
"description": { "type": "string", "example": "An example role for the spec" },
2071+
"uuid": { "type": "string", "format":"uuid", "example": "a8d33564-628f-4eba-bd59-6a2948bfb31e" },
2072+
"platform_default": { "type": "boolean", "example": true },
2073+
"admin_default": { "type": "boolean", "example": false },
2074+
"permissions": {
2075+
"type": "array",
2076+
"items":{
2077+
"type": "string",
2078+
"example": "application | resource | verb"
2079+
}
2080+
}
2081+
}
2082+
}
2083+
}
2084+
}
2085+
}
2086+
}
2087+
},
2088+
"required": ["username", "email_address", "groups"]
2089+
}
2090+
}
2091+
}
2092+
},
2093+
"400": {
2094+
"description": "Invalid request - bad input",
2095+
"content": {
2096+
"application/json": {
2097+
"schema": {
2098+
"$ref": "#/components/schemas/ErrorSingle"
2099+
}
2100+
}
2101+
}
2102+
},
2103+
"404": {
2104+
"description": "Not found - user not found",
2105+
"content": {
2106+
"application/json": {
2107+
"schema": {
2108+
"$ref": "#/components/schemas/ErrorSingle"
2109+
}
2110+
}
2111+
}
2112+
},
2113+
"405": {
2114+
"description": "Invalid method - invalid http method used, only GET allowed",
2115+
"content": {
2116+
"application/json": {
2117+
"schema": {
2118+
"$ref": "#/components/schemas/ErrorSingle"
2119+
}
2120+
}
2121+
}
2122+
},
2123+
"500": {
2124+
"description": "Internal error - unexpected internal server error",
2125+
"content": {
2126+
"application/json": {
2127+
"schema": {
2128+
"$ref": "#/components/schemas/ErrorSingle"
2129+
}
2130+
}
2131+
}
2132+
}
2133+
}
2134+
}
2135+
},
20182136
"/_s2s/workspaces/ungrouped/": {
20192137
"post": {
20202138
"tags": [
@@ -2111,6 +2229,17 @@
21112229
}
21122230
}
21132231
},
2232+
"ErrorSingle": {
2233+
"required": [
2234+
"error"
2235+
],
2236+
"properties": {
2237+
"error": {
2238+
"type": "string",
2239+
"example": "Invalid request - bad input provided"
2240+
}
2241+
}
2242+
},
21142243
"Error403": {
21152244
"required": [
21162245
"errors"

Diff for: rbac/internal/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
path("api/utils/reset_imported_tenants/", views.reset_imported_tenants),
9797
path("api/utils/resource_definitions/", views.correct_resource_definitions),
9898
path("api/utils/principal/", views.principal_removal),
99+
path("api/utils/user_lookup/", views.user_lookup),
99100
]
100101

101102
urlpatterns.extend(integration_urlpatterns)

Diff for: rbac/internal/utils.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,20 @@ def build_internal_user(request, json_rh_auth):
3636
user = User()
3737
valid_identity_types = ["Associate", "X509"]
3838
try:
39-
if not json_rh_auth["identity"]["type"] in valid_identity_types:
39+
identity_type = json_rh_auth["identity"]["type"]
40+
if identity_type not in valid_identity_types:
41+
logger.debug(
42+
f"User identity type is not valid: '{identity_type}'. Valid types are: {valid_identity_types}"
43+
)
4044
return None
4145
user.username = json_rh_auth["identity"].get("associate", {}).get("email", "system")
4246
user.admin = True
4347
user.org_id = resolve(request.path).kwargs.get("org_id")
4448
return user
4549
except KeyError:
50+
logger.debug(
51+
f"Identity object is missing 'identity.type' attribute. Valid options are: {valid_identity_types}"
52+
)
4653
return None
4754

4855

0 commit comments

Comments
 (0)