Skip to content

Commit 54b71df

Browse files
[WD-26091] feat: Assign user|admin role based on launchpad team membership, upon SSO login (#230)
1 parent 49b58bf commit 54b71df

File tree

6 files changed

+68
-1
lines changed

6 files changed

+68
-1
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Added role column to users table
2+
3+
Revision ID: 14729444f282
4+
Revises: 1b6109065dba
5+
Create Date: 2025-08-26 17:15:11.023451
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "14729444f282"
15+
down_revision = "1b6109065dba"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.add_column("users", sa.Column("role", sa.String(), default="user"))
22+
23+
24+
def downgrade():
25+
op.drop_column("users", "role")

static/client/services/api/types/users.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface IUser {
55
jobTitle: string;
66
department: string;
77
team: string;
8+
role: string;
89
}
910

1011
export interface IUsersResponse {

webapp/helper.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def get_or_create_user_id(user):
2727
department=user.get("department"),
2828
job_title=user.get("jobTitle"),
2929
hrc_id=user.get("id"),
30+
role=user.get("role"),
3031
)
3132

3233
return user_exists.id

webapp/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ class User(db.Model, DateTimeMixin):
134134
department: str = Column(String)
135135
hrc_id: int = Column(Integer)
136136
job_title: str = Column(String)
137+
role: str = Column(String)
137138

138139
webpages = relationship("Webpage", back_populates="owner")
139140
reviewers = relationship("Reviewer", back_populates="user")

webapp/routes/user.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def current_user():
112112
"team": user.team,
113113
"department": user.department,
114114
"jobTitle": user.job_title,
115+
"role": user.role,
115116
}
116117
),
117118
200,

webapp/sso.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
from flask_openid import OpenID
77

88
from webapp.helper import get_or_create_user_id, get_user_from_directory_by_key
9-
from webapp.models import User
9+
from webapp.models import User, db
1010

1111
SSO_LOGIN_URL = "https://login.ubuntu.com"
1212
# private teams like canonical are not returned in response atm
1313
# so temporarily need to add multiple subset public teams
14+
15+
SSO_ADMIN_TEAM = "content-system-admins"
1416
SSO_TEAM = (
1517
"canonical",
1618
"canonical-content-people",
1719
"pga-admins",
1820
"canonical-webmonkeys",
21+
SSO_ADMIN_TEAM,
1922
)
2023
DISABLE_SSO = os.environ.get("DISABLE_SSO") or os.environ.get(
2124
"FLASK_DISABLE_SSO"
@@ -45,20 +48,32 @@ def after_login(resp):
4548
if not (set(SSO_TEAM) & set(resp.extensions["lp"].is_member)):
4649
flask.abort(403)
4750

51+
# check if user is admin
52+
role = (
53+
"admin"
54+
if SSO_ADMIN_TEAM in resp.extensions["lp"].is_member
55+
else "user"
56+
)
57+
4858
# find the user in database
4959
user = User.query.filter_by(email=resp.email).first()
60+
if user and user.role != role:
61+
user.role = role
62+
db.session.commit()
5063
if not user:
5164
# fetch user record from directory
5265
response = get_user_from_directory_by_key("email", resp.email)
5366

5467
if response.status_code == 200:
5568
user = response.json().get("data", {}).get("employees", [])[0]
69+
user["role"] = role
5670
# save user in users table
5771
get_or_create_user_id(user)
5872

5973
flask.session["openid"] = {
6074
"identity_url": resp.identity_url,
6175
"email": resp.email,
76+
"role": role,
6277
}
6378

6479
return flask.redirect(open_id.get_next_url())
@@ -101,3 +116,26 @@ def is_user_logged_in(*args, **kwargs):
101116
return flask.redirect("/login_page?next=" + flask.request.path)
102117

103118
return is_user_logged_in
119+
120+
121+
def is_admin(func):
122+
"""
123+
Decorator that checks if a user is an admin user
124+
"""
125+
126+
@functools.wraps(func)
127+
def is_admin_user(*args, **kwargs):
128+
if (
129+
"openid" in flask.session
130+
and flask.session.get("openid")["role"] == "admin"
131+
):
132+
return func(*args, **kwargs)
133+
134+
return (
135+
flask.jsonify(
136+
{"error": "This operation requires admin privileges"}
137+
),
138+
403,
139+
)
140+
141+
return is_admin_user

0 commit comments

Comments
 (0)