Skip to content

Add role owner view for expiring roles page #300

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1844,6 +1844,12 @@
"required": false,
"type": "string"
},
{
"in": "query",
"name": "role_owner_id",
"required": false,
"type": "string"
},
{
"in": "query",
"name": "start_date",
Expand Down
38 changes: 38 additions & 0 deletions api/views/resources/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,44 @@ def get(self) -> ResponseReturnValue:
group_alias.name.asc() if order_by != "moniker" else nullslast(RoleGroupMap.created_at.asc()),
)

if "role_owner_id" in search_args:
role_owner_id = search_args["role_owner_id"]
if role_owner_id == "@me":
role_owner_id = g.current_user_id
role_owner = (
OktaUser.query.filter(db.or_(OktaUser.id == role_owner_id, OktaUser.email.ilike(role_owner_id)))
.order_by(nullsfirst(OktaUser.deleted_at.desc()))
.first_or_404()
)
role_group_alias = aliased(RoleGroup)
owner_role_ownerships = (
OktaUserGroupMember.query.options(joinedload(OktaUserGroupMember.group.of_type(RoleGroup)))
.filter(OktaUserGroupMember.user_id == role_owner.id)
.filter(OktaUserGroupMember.is_owner.is_(True))
.join(OktaUserGroupMember.group.of_type(role_group_alias))
.filter(
db.or_(
OktaUserGroupMember.ended_at.is_(None),
OktaUserGroupMember.ended_at > db.func.now(),
)
)
)

# https://stackoverflow.com/questions/4186062/sqlalchemy-order-by-descending#comment52902932_9964966
query = query.filter(
RoleGroupMap.role_group_id.in_(
[o.group_id for o in owner_role_ownerships.with_entities(OktaUserGroupMember.group_id).all()]
)
).order_by(
nulls_order(
getattr(
group_alias.name if order_by == "moniker" else getattr(RoleGroupMap, order_by),
"desc" if order_direction else "asc",
)()
),
group_alias.name.asc() if order_by != "moniker" else nullslast(RoleGroupMap.created_at.asc()),
)

# Implement basic search with the "q" url parameter
if "q" in search_args and len(search_args["q"]) > 0:
like_search = f"%{search_args['q']}%"
Expand Down
1 change: 1 addition & 0 deletions api/views/schemas/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class SearchGroupRoleAuditPaginationRequestSchema(SearchAuditPaginationRequestSc
group_id = fields.String(load_only=True)
role_id = fields.String(load_only=True)
owner_id = fields.String(load_only=True)
role_owner_id = fields.String(load_only=True)
app_owner = fields.Boolean(load_only=True)
start_date = fields.Int(load_only=True)
end_date = fields.Int(load_only=True)
Expand Down
1 change: 1 addition & 0 deletions src/api/apiComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ export type GetGroupRoleAuditsQueryParams = {
group_id?: string;
role_id?: string;
owner_id?: string;
role_owner_id?: string;
start_date?: number;
end_date?: number;
};
Expand Down
9 changes: 8 additions & 1 deletion src/components/NavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import ExpiringGroupsIcon from '@mui/icons-material/RunningWithErrors';
import ExpiringRolesIcon from '@mui/icons-material/HeartBroken';
import ExpiringMyAccess from '@mui/icons-material/AccountBox';
import ExpiringOwnedByMe from '@mui/icons-material/AccountTree';
import ExpiringRolesOwnedByMe from '@mui/icons-material/HowToReg';
import ExpiringAll from '@mui/icons-material/SwitchAccount';
import RoleRequestIcon from '@mui/icons-material/WorkHistory';

Expand Down Expand Up @@ -157,10 +158,16 @@ export default function NavItems(props: NavItemsProps) {
<List disablePadding>
<ListItemLink
to="/expiring-roles?owner_id=@me"
displayText="Owned by Me"
displayText="Owned Groups"
displayIcon={<ExpiringOwnedByMe />}
sx={{pl: 4}}
/>
<ListItemLink
to="/expiring-roles?role_owner_id=@me"
displayText="Owned Roles"
displayIcon={<ExpiringRolesOwnedByMe />}
sx={{pl: 4}}
/>
<ListItemLink to="/expiring-roles" displayText="All" displayIcon={<ExpiringAll />} sx={{pl: 4}} />
</List>
</Collapse>
Expand Down
5 changes: 3 additions & 2 deletions src/pages/role_requests/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ interface CreateRequestProps {
enabled: boolean;
currentUser: OktaUser;
role?: RoleGroup;
group?: PolymorphicGroup;
group?: OktaGroup | AppGroup;
owner?: boolean;
renew?: boolean;
}
Expand All @@ -517,8 +517,9 @@ export default function CreateRequest(props: CreateRequestProps) {
const [open, setOpen] = React.useState<boolean>(false);

if (
props.role?.deleted_at != null ||
props.group?.deleted_at != null ||
(props.group != null && canManageGroup(props.currentUser, props.group)) ||
(props.role != null && !canManageGroup(props.currentUser, props.role)) ||
props.group?.is_managed == false
) {
return null;
Expand Down
14 changes: 14 additions & 0 deletions src/pages/roles/Expiring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import dayjs, {Dayjs} from 'dayjs';

import BulkRenewal from './BulkRenewal';
import NotFound from '../NotFound';
import CreateRoleRequest from '../role_requests/Create';
import {useGetGroupRoleAudits, useGetGroups} from '../../api/apiComponents';
import ChangeTitle from '../../tab-title';
import {useCurrentUser} from '../../authentication';
Expand All @@ -45,6 +46,7 @@ export default function ExpiringRoless() {
const [orderDirection, setOrderDirection] = React.useState<OrderDirection>('asc');
const [searchParams, setSearchParams] = useSearchParams();
const [ownerId, setOwnerId] = React.useState<string | null>(null);
const [roleOwnerId, setRoleOwnerId] = React.useState<string | null>(null);
const [searchQuery, setSearchQuery] = React.useState<string | null>(null);
const [searchInput, setSearchInput] = React.useState('');
const [page, setPage] = React.useState(0);
Expand All @@ -59,6 +61,7 @@ export default function ExpiringRoless() {
setOrderBy((searchParams.get('order_by') as OrderBy) ?? 'ended_at');
setOrderDirection((searchParams.get('order_desc') ?? 'true') === 'true' ? 'asc' : 'desc');
setOwnerId(searchParams.get('owner_id') ?? null);
setRoleOwnerId(searchParams.get('role_owner_id') ?? null);
setSearchQuery(searchParams.get('q') ?? null);
if (searchInput == '') {
setSearchInput(searchParams.get('q') ?? '');
Expand All @@ -84,6 +87,7 @@ export default function ExpiringRoless() {
orderDirection == null ? null : {order_desc: orderDirection == 'desc' ? 'true' : 'false'},
searchQuery == null ? null : {q: searchQuery},
ownerId == null ? null : {owner_id: ownerId},
roleOwnerId == null ? null : {role_owner_id: roleOwnerId},
filterActive == null ? null : {active: filterActive},
{app_owner: filterAppOwnership},
startDate == null ? null : {start_date: startDate.unix()},
Expand Down Expand Up @@ -367,6 +371,16 @@ export default function ExpiringRoless() {
<TableCell align="center">
<BulkRenewal rows={rows.filter((row) => canManageGroup(currentUser, row.group))} select={row.id} />
</TableCell>
) : roleOwnerId || canManageGroup(currentUser, row.role_group) ? (
<TableCell align="center">
<CreateRoleRequest
currentUser={currentUser}
enabled
role={row.role_group}
group={row.group}
owner={row.is_owner}
renew></CreateRoleRequest>
</TableCell>
) : (
<TableCell></TableCell>
)}
Expand Down