Move source and state from membership to profile
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<b6eef6dfea77fdf5e4e151eef11f4484>>
|
||||
* @generated SignedSource<<d8cb4fb18045bbd1eb91c3dba7c99a79>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -222,6 +222,20 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v12/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -259,20 +273,6 @@ return {
|
||||
"name": "role",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "canUpdate",
|
||||
"args": [
|
||||
@@ -430,12 +430,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "25aa17e466fdec627ba3d24d5230301b",
|
||||
"cacheID": "cc77d44877ae736c858e7458b525d75a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PeopleListFragment_RefetchQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
|
||||
"text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n kind\n position\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6e9b0a4fbf6ce65f11f2367f2ec5b6d4>>
|
||||
* @generated SignedSource<<67410bc0aa6e9426f4534a0938429b6e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -10,9 +10,8 @@
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
|
||||
export type MembershipSource = "MANUAL" | "SAML" | "SCIM";
|
||||
export type MembershipState = "ACTIVE" | "INACTIVE";
|
||||
export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||
export type ProfileState = "ACTIVE" | "INACTIVE";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type PeopleListItemFragment$data = {
|
||||
readonly canUpdate: boolean;
|
||||
@@ -28,10 +27,10 @@ export type PeopleListItemFragment$data = {
|
||||
readonly canUpdate: boolean;
|
||||
readonly id: string;
|
||||
readonly role: MembershipRole;
|
||||
readonly source: MembershipSource;
|
||||
readonly state: MembershipState;
|
||||
};
|
||||
readonly position: string | null | undefined;
|
||||
readonly source: string;
|
||||
readonly state: ProfileState;
|
||||
readonly " $fragmentType": "PeopleListItemFragment";
|
||||
};
|
||||
export type PeopleListItemFragment$key = {
|
||||
@@ -54,6 +53,20 @@ return {
|
||||
"name": "PeopleListItemFragment",
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -93,20 +106,6 @@ return {
|
||||
"name": "role",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "canUpdate",
|
||||
"args": [
|
||||
@@ -186,6 +185,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "89702fbdf02294212269adb22efb65fe";
|
||||
(node as any).hash = "ad9ed05edf606ec084c6f53462530b8b";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<e628337d33e88ca5cfe6e45a55eebe32>>
|
||||
* @generated SignedSource<<7f025767ad277b8b8de6cbe9b1490312>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -345,6 +345,20 @@ return {
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v10/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v11/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
@@ -370,20 +384,6 @@ return {
|
||||
"selections": [
|
||||
(v10/*: any*/),
|
||||
(v12/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "source",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "canUpdate",
|
||||
"args": [
|
||||
@@ -552,12 +552,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "4303ec6c2fa72181d747bb9053446aaa",
|
||||
"cacheID": "b67de1f40d556a5e4a6d78104e2eb224",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "PeoplePageQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...PeopleListFragment_8lnpd\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\n ...InvitationListFragment_1PypFi\n }\n id\n }\n}\n\nfragment InvitationListFragment_1PypFi on Organization {\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n fullName\n kind\n position\n membership {\n id\n role\n source\n state\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
|
||||
"text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canInviteUser: permission(action: \"iam:invitation:create\")\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n }\n ...PeopleListFragment_8lnpd\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n totalCount\n __typename\n }\n ...InvitationListFragment_1PypFi\n }\n id\n }\n}\n\nfragment InvitationListFragment_1PypFi on Organization {\n invitations(first: 20, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n kind\n position\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6bc4cdb83f5825b5d606fe579fa8f7a8>>
|
||||
* @generated SignedSource<<f347bcc313be984e835dd6518091a71c>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -16,10 +16,8 @@ export type SCIMEventListItemFragment$data = {
|
||||
readonly ipAddress: string;
|
||||
readonly method: string;
|
||||
readonly path: string;
|
||||
readonly profile: {
|
||||
readonly fullName: string;
|
||||
} | null | undefined;
|
||||
readonly statusCode: number;
|
||||
readonly userName: string;
|
||||
readonly " $fragmentType": "SCIMEventListItemFragment";
|
||||
};
|
||||
export type SCIMEventListItemFragment$key = {
|
||||
@@ -78,19 +76,8 @@ const node: ReaderFragment = {
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Profile",
|
||||
"kind": "LinkedField",
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "userName",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
@@ -98,6 +85,6 @@ const node: ReaderFragment = {
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "b38af9ac9b660d3aab75e00e2c55dfb6";
|
||||
(node as any).hash = "9df2e986643ab90589ebc6e4631cde24";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<22d63f81020d1e99f7cdb84fc23913a4>>
|
||||
* @generated SignedSource<<08f3ffe97dd35388d5ce01977170773b>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -224,20 +224,8 @@ return {
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Profile",
|
||||
"kind": "LinkedField",
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/)
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "userName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
@@ -315,12 +303,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "58b36de1b4c1f54de6433a95b297b570",
|
||||
"cacheID": "5e80fcd5f3479d5b6bce65b59364d3f7",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "SCIMEventListPaginationQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query SCIMEventListPaginationQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...SCIMEventListFragment_pbnwq\n id\n }\n}\n\nfragment SCIMEventListFragment_pbnwq on SCIMConfiguration {\n events(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n ...SCIMEventListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment SCIMEventListItemFragment on SCIMEvent {\n method\n path\n statusCode\n errorMessage\n ipAddress\n createdAt\n profile {\n fullName\n id\n }\n}\n"
|
||||
"text": "query SCIMEventListPaginationQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...SCIMEventListFragment_pbnwq\n id\n }\n}\n\nfragment SCIMEventListFragment_pbnwq on SCIMConfiguration {\n events(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n ...SCIMEventListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment SCIMEventListItemFragment on SCIMEvent {\n method\n path\n statusCode\n errorMessage\n ipAddress\n createdAt\n userName\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<339e086642b09eb0bacbc51601014fe7>>
|
||||
* @generated SignedSource<<164d2e51d3c593d5effe16958d2d8f0e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -277,20 +277,8 @@ return {
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Profile",
|
||||
"kind": "LinkedField",
|
||||
"name": "profile",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fullName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"kind": "ScalarField",
|
||||
"name": "userName",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: any*/)
|
||||
@@ -404,12 +392,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3d49152bd3fd384a0b1de3d012268ebe",
|
||||
"cacheID": "b98c546a2c8fc038c121c21f3b495a7d",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "SCIMSettingsPageQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query SCIMSettingsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n scimConfiguration {\n id\n bridge {\n id\n }\n ...SCIMEventListFragment\n }\n ...SCIMConfigurationFragment\n ...ConnectorListFragment\n }\n id\n }\n}\n\nfragment ConnectorListFragment on Organization {\n scimConfiguration {\n ...GoogleWorkspaceConnectorFragment\n id\n }\n}\n\nfragment GoogleWorkspaceConnectorFragment on SCIMConfiguration {\n id\n bridge {\n id\n excludedUserNames\n connector {\n id\n createdAt\n }\n }\n}\n\nfragment SCIMConfigurationFragment on Organization {\n canCreateSCIMConfiguration: permission(action: \"iam:scim-configuration:create\")\n canDeleteSCIMConfiguration: permission(action: \"iam:scim-configuration:delete\")\n scimConfiguration {\n id\n endpointUrl\n bridge {\n id\n }\n }\n}\n\nfragment SCIMEventListFragment on SCIMConfiguration {\n events(first: 20) {\n edges {\n node {\n id\n ...SCIMEventListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment SCIMEventListItemFragment on SCIMEvent {\n method\n path\n statusCode\n errorMessage\n ipAddress\n createdAt\n profile {\n fullName\n id\n }\n}\n"
|
||||
"text": "query SCIMSettingsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n scimConfiguration {\n id\n bridge {\n id\n }\n ...SCIMEventListFragment\n }\n ...SCIMConfigurationFragment\n ...ConnectorListFragment\n }\n id\n }\n}\n\nfragment ConnectorListFragment on Organization {\n scimConfiguration {\n ...GoogleWorkspaceConnectorFragment\n id\n }\n}\n\nfragment GoogleWorkspaceConnectorFragment on SCIMConfiguration {\n id\n bridge {\n id\n excludedUserNames\n connector {\n id\n createdAt\n }\n }\n}\n\nfragment SCIMConfigurationFragment on Organization {\n canCreateSCIMConfiguration: permission(action: \"iam:scim-configuration:create\")\n canDeleteSCIMConfiguration: permission(action: \"iam:scim-configuration:delete\")\n scimConfiguration {\n id\n endpointUrl\n bridge {\n id\n }\n }\n}\n\nfragment SCIMEventListFragment on SCIMConfiguration {\n events(first: 20) {\n edges {\n node {\n id\n ...SCIMEventListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment SCIMEventListItemFragment on SCIMEvent {\n method\n path\n statusCode\n errorMessage\n ipAddress\n createdAt\n userName\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -24,14 +24,14 @@ import { EditMemberDialog } from "../../settings/_components/EditMemberDialog";
|
||||
const fragment = graphql`
|
||||
fragment PeopleListItemFragment on Profile {
|
||||
id
|
||||
source
|
||||
state
|
||||
fullName
|
||||
kind
|
||||
position
|
||||
membership @required(action: THROW) {
|
||||
id
|
||||
role
|
||||
source
|
||||
state
|
||||
canUpdate: permission(action: "iam:membership:update")
|
||||
canDelete: permission(action: "iam:membership-profile:delete")
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export function MemberListItem(props: {
|
||||
|
||||
const profile = useFragment<PeopleListItemFragment$key>(fragment, fKey);
|
||||
|
||||
const isInactive = profile.membership.state === "INACTIVE";
|
||||
const isInactive = profile.state === "INACTIVE";
|
||||
|
||||
const [removeMembership, isRemoving] = useMutationWithToasts(
|
||||
removeMemberMutation,
|
||||
@@ -118,7 +118,7 @@ export function MemberListItem(props: {
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
{profile.identity.email}
|
||||
<Badge variant="info">{profile.membership.source}</Badge>
|
||||
<Badge variant="info">{profile.source}</Badge>
|
||||
</div>
|
||||
</Td>
|
||||
<Td>{profile.kind}</Td>
|
||||
|
||||
@@ -14,9 +14,7 @@ const SCIMEventListItemFragment = graphql`
|
||||
errorMessage
|
||||
ipAddress
|
||||
createdAt
|
||||
profile {
|
||||
fullName
|
||||
}
|
||||
userName
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -89,8 +87,7 @@ export function SCIMEventListItem(props: {
|
||||
<div className="flex gap-8 text-sm">
|
||||
<div>
|
||||
<span className="text-txt-secondary">User: </span>
|
||||
{/* FIXME */}
|
||||
{/* <span>{event.membership?.profile?.fullName || "-"}</span> */}
|
||||
<span>{event.userName || "-"}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-txt-secondary">IP Address: </span>
|
||||
|
||||
@@ -25,24 +25,17 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
Membership struct {
|
||||
ID gid.GID `db:"id"`
|
||||
IdentityID gid.GID `db:"identity_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Role MembershipRole `db:"role"`
|
||||
Source MembershipSource `db:"source"`
|
||||
State MembershipState `db:"state"`
|
||||
// FIXME: remove after scim is based on profile
|
||||
EmailAddress mail.Addr `db:"-"`
|
||||
FullName string `db:"-"`
|
||||
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
IdentityID gid.GID `db:"identity_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Role MembershipRole `db:"role"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
Memberships []*Membership
|
||||
@@ -66,8 +59,6 @@ SELECT
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -109,8 +100,6 @@ INSERT INTO
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
@@ -120,8 +109,6 @@ VALUES (
|
||||
@identity_id,
|
||||
@organization_id,
|
||||
@role,
|
||||
@source,
|
||||
@state,
|
||||
@created_at,
|
||||
@updated_at
|
||||
);
|
||||
@@ -133,8 +120,6 @@ VALUES (
|
||||
"identity_id": m.IdentityID,
|
||||
"organization_id": m.OrganizationID,
|
||||
"role": m.Role,
|
||||
"source": m.Source,
|
||||
"state": m.State,
|
||||
"created_at": m.CreatedAt,
|
||||
"updated_at": m.UpdatedAt,
|
||||
}
|
||||
@@ -168,8 +153,6 @@ SELECT
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -209,8 +192,7 @@ func (m *Membership) AuthorizationAttributes(ctx context.Context, conn pg.Conn)
|
||||
SELECT
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source
|
||||
role
|
||||
FROM
|
||||
iam_memberships
|
||||
WHERE
|
||||
@@ -221,12 +203,10 @@ LIMIT 1;
|
||||
var identityID gid.GID
|
||||
var organizationID gid.GID
|
||||
var role MembershipRole
|
||||
var source MembershipSource
|
||||
if err := conn.QueryRow(ctx, q, m.ID).Scan(
|
||||
&identityID,
|
||||
&organizationID,
|
||||
&role,
|
||||
&source,
|
||||
); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
@@ -238,7 +218,6 @@ LIMIT 1;
|
||||
"identity_id": identityID.String(),
|
||||
"organization_id": organizationID.String(),
|
||||
"role": role.String(),
|
||||
"source": source.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -255,8 +234,6 @@ SELECT
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
@@ -299,8 +276,6 @@ UPDATE
|
||||
iam_memberships
|
||||
SET
|
||||
role = @role,
|
||||
source = @source,
|
||||
state = @state,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
id = @id
|
||||
@@ -312,8 +287,6 @@ WHERE
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": m.ID,
|
||||
"role": m.Role,
|
||||
"source": m.Source,
|
||||
"state": m.State,
|
||||
"updated_at": m.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
@@ -358,180 +331,51 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memberships) LoadByOrganizationID(
|
||||
func (m *Membership) LoadActiveByIdentityIDAndOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
identityID gid.GID,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[MembershipOrderField],
|
||||
filter *MembershipFilter,
|
||||
) error {
|
||||
query := `
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
iam_memberships
|
||||
WHERE
|
||||
%s
|
||||
AND %s
|
||||
organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
query = fmt.Sprintf(query, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query memberships: %w", err)
|
||||
}
|
||||
|
||||
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Membership])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect memberships: %w", err)
|
||||
}
|
||||
|
||||
*m = memberships
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memberships) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
filter *MembershipFilter,
|
||||
) (int, error) {
|
||||
query := `
|
||||
SELECT
|
||||
COUNT(*)
|
||||
m.id,
|
||||
m.identity_id,
|
||||
m.organization_id,
|
||||
m.role,
|
||||
m.created_at,
|
||||
m.updated_at
|
||||
FROM
|
||||
iam_memberships m
|
||||
JOIN
|
||||
identities i ON m.identity_id = i.id
|
||||
INNER JOIN iam_membership_profiles p
|
||||
ON p.identity_id = m.identity_id AND p.organization_id = m.organization_id
|
||||
WHERE
|
||||
m.organization_id = @organization_id
|
||||
AND m.%s
|
||||
AND %s
|
||||
p.state = @state
|
||||
AND m.identity_id = @identity_id
|
||||
AND m.organization_id = @organization_id
|
||||
LIMIT 1
|
||||
`
|
||||
query = fmt.Sprintf(query, scope.SQLFragment(), filter.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state": ProfileStateActive,
|
||||
"identity_id": identityID,
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
maps.Copy(args, filter.SQLArguments())
|
||||
row := conn.QueryRow(ctx, query, args)
|
||||
var count int
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, fmt.Errorf("cannot count memberships: %w", err)
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (m *Memberships) CountByIdentityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
identityID gid.GID,
|
||||
) (int, error) {
|
||||
query := `
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
iam_memberships
|
||||
WHERE
|
||||
identity_id = @identity_id
|
||||
AND state = 'ACTIVE'
|
||||
`
|
||||
args := pgx.StrictNamedArgs{
|
||||
"identity_id": identityID,
|
||||
}
|
||||
|
||||
row := conn.QueryRow(ctx, query, args)
|
||||
var count int
|
||||
if err := row.Scan(&count); err != nil {
|
||||
return 0, fmt.Errorf("cannot count memberships: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (m *Memberships) LoadAllByIdentityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
identityID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
identity_id,
|
||||
organization_id,
|
||||
role,
|
||||
source,
|
||||
state,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
iam_memberships
|
||||
WHERE
|
||||
identity_id = $1
|
||||
;
|
||||
`
|
||||
|
||||
rows, err := conn.Query(ctx, q, identityID)
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query memberships: %w", err)
|
||||
}
|
||||
|
||||
memberships, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Membership])
|
||||
membership, err := pgx.CollectExactlyOneRow(rows, pgx.RowToAddrOfStructByName[Membership])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect memberships: %w", err)
|
||||
}
|
||||
|
||||
*m = memberships
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Memberships) ResetSCIMSources(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE iam_memberships
|
||||
SET
|
||||
source = 'MANUAL',
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND source = 'SCIM'
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"organization_id": organizationID,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot reset SCIM membership sources: %w", err)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect membership: %w", err)
|
||||
}
|
||||
|
||||
*m = *membership
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
)
|
||||
|
||||
type MembershipFilter struct {
|
||||
email *mail.Addr
|
||||
role *MembershipRole
|
||||
state *MembershipState
|
||||
source *MembershipSource
|
||||
}
|
||||
|
||||
func NewMembershipFilter() *MembershipFilter {
|
||||
return &MembershipFilter{}
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) WithEmail(email *mail.Addr) *MembershipFilter {
|
||||
f.email = email
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) Email() *mail.Addr {
|
||||
return f.email
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) WithRole(role MembershipRole) *MembershipFilter {
|
||||
f.role = &role
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) Role() *MembershipRole {
|
||||
return f.role
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) WithState(state MembershipState) *MembershipFilter {
|
||||
f.state = &state
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) State() *MembershipState {
|
||||
return f.state
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) WithSource(source MembershipSource) *MembershipFilter {
|
||||
f.source = &source
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) Source() *MembershipSource {
|
||||
return f.source
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
return pgx.StrictNamedArgs{
|
||||
"filter_email": f.email,
|
||||
"filter_role": f.role,
|
||||
"filter_state": f.state,
|
||||
"filter_source": f.source,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *MembershipFilter) SQLFragment() string {
|
||||
return `
|
||||
(
|
||||
CASE
|
||||
WHEN @filter_email::text IS NOT NULL THEN
|
||||
i.email_address = @filter_email::text
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @filter_role::text IS NOT NULL THEN
|
||||
m.role = @filter_role::authz_role
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @filter_state::text IS NOT NULL THEN
|
||||
m.state = @filter_state::membership_state
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @filter_source::text IS NOT NULL THEN
|
||||
m.source = @filter_source::text
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
@@ -34,6 +34,8 @@ type (
|
||||
IdentityID gid.GID `db:"identity_id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
EmailAddress mail.Addr `db:"email_address"`
|
||||
Source ProfileSource `db:"source"`
|
||||
State ProfileState `db:"state"`
|
||||
FullName string `db:"full_name"`
|
||||
Kind MembershipProfileKind `db:"kind"`
|
||||
AdditionalEmailAddresses mail.Addrs `db:"additional_email_addresses"`
|
||||
@@ -93,6 +95,8 @@ SELECT
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -149,6 +153,8 @@ SELECT
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -208,6 +214,8 @@ SELECT
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -257,30 +265,35 @@ func (p *MembershipProfiles) LoadByOrganizationID(
|
||||
q := `
|
||||
WITH profiles AS (
|
||||
SELECT
|
||||
id,
|
||||
identity_id,
|
||||
organization_id,
|
||||
full_name,
|
||||
kind,
|
||||
additional_email_addresses,
|
||||
position,
|
||||
contract_start_date,
|
||||
contract_end_date,
|
||||
created_at,
|
||||
updated_at
|
||||
p.id,
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
p.position,
|
||||
p.contract_start_date,
|
||||
p.contract_end_date,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM
|
||||
iam_membership_profiles
|
||||
iam_membership_profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
p.%s
|
||||
AND p.organization_id = @organization_id
|
||||
AND %s
|
||||
)
|
||||
SELECT
|
||||
p.id,
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -291,7 +304,8 @@ SELECT
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
WHERE
|
||||
%s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
@@ -317,7 +331,6 @@ INNER JOIN identities i ON i.id = p.identity_id
|
||||
func (p *MembershipProfiles) LoadByIdentityID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
identityID gid.GID,
|
||||
cursor *page.Cursor[MembershipProfileOrderField],
|
||||
filter *MembershipProfileFilter,
|
||||
@@ -325,29 +338,34 @@ func (p *MembershipProfiles) LoadByIdentityID(
|
||||
q := `
|
||||
WITH profiles AS (
|
||||
SELECT
|
||||
id,
|
||||
identity_id,
|
||||
organization_id,
|
||||
full_name,
|
||||
kind,
|
||||
additional_email_addresses,
|
||||
position,
|
||||
contract_start_date,
|
||||
contract_end_date,
|
||||
created_at,
|
||||
updated_at
|
||||
p.id,
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
p.position,
|
||||
p.contract_start_date,
|
||||
p.contract_end_date,
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM
|
||||
iam_membership_profiles
|
||||
iam_membership_profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
WHERE
|
||||
%s
|
||||
AND identity_id = @identity_id
|
||||
p.identity_id = @identity_id
|
||||
AND %s
|
||||
)
|
||||
SELECT
|
||||
p.id,
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -358,16 +376,14 @@ SELECT
|
||||
p.created_at,
|
||||
p.updated_at
|
||||
FROM profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
INNER JOIN organizations o ON o.id = p.organization_id
|
||||
WHERE
|
||||
%s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
q = fmt.Sprintf(q, filter.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"identity_id": identityID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
@@ -600,6 +616,8 @@ WITH attendees AS (
|
||||
p.identity_id,
|
||||
p.organization_id,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.full_name,
|
||||
p.kind,
|
||||
p.additional_email_addresses,
|
||||
@@ -624,6 +642,8 @@ SELECT
|
||||
organization_id,
|
||||
kind,
|
||||
email_address,
|
||||
source,
|
||||
state,
|
||||
full_name,
|
||||
additional_email_addresses,
|
||||
position,
|
||||
@@ -684,6 +704,8 @@ SELECT
|
||||
p.kind,
|
||||
p.full_name,
|
||||
i.email_address,
|
||||
p.source,
|
||||
p.state,
|
||||
p.additional_email_addresses,
|
||||
p.position,
|
||||
p.contract_start_date,
|
||||
@@ -725,10 +747,11 @@ func (p *MembershipProfiles) CountByIdentityID(
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
iam_membership_profiles
|
||||
iam_membership_profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
WHERE
|
||||
%s
|
||||
AND identity_id = @identity_id
|
||||
AND p.identity_id = @identity_id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, filter.SQLFragment())
|
||||
@@ -758,11 +781,12 @@ func (p *MembershipProfiles) CountByOrganizationID(
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
iam_membership_profiles
|
||||
iam_membership_profiles p
|
||||
INNER JOIN identities i ON i.id = p.identity_id
|
||||
WHERE
|
||||
%s
|
||||
p.%s
|
||||
AND %s
|
||||
AND organization_id = @organization_id
|
||||
AND p.organization_id = @organization_id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||
@@ -782,6 +806,45 @@ WHERE
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (p *MembershipProfiles) CountActiveOwnerByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(*)
|
||||
FROM
|
||||
iam_membership_profiles p
|
||||
INNER JOIN iam_memberships m ON m.identity_id = p.identity_id AND m.organization_id = p.organization_id
|
||||
WHERE
|
||||
%s
|
||||
AND p.organization_id = @organization_id
|
||||
AND p.state = @state
|
||||
AND m.role = @role
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"state": ProfileStateActive,
|
||||
"role": MembershipRoleOwner,
|
||||
"organization_id": organizationID,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
row := conn.QueryRow(ctx, q, args)
|
||||
|
||||
var count int
|
||||
err := row.Scan(&count)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot collect count: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (p *MembershipProfile) Insert(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
@@ -793,6 +856,8 @@ INSERT INTO
|
||||
id,
|
||||
identity_id,
|
||||
organization_id,
|
||||
source,
|
||||
state,
|
||||
full_name,
|
||||
kind,
|
||||
additional_email_addresses,
|
||||
@@ -807,6 +872,8 @@ VALUES (
|
||||
@id,
|
||||
@identity_id,
|
||||
@organization_id,
|
||||
@source,
|
||||
@state,
|
||||
@full_name,
|
||||
@kind,
|
||||
COALESCE(@additional_email_addresses, '{}'::CITEXT[]),
|
||||
@@ -823,6 +890,8 @@ VALUES (
|
||||
"id": p.ID,
|
||||
"identity_id": p.IdentityID,
|
||||
"organization_id": p.OrganizationID,
|
||||
"source": p.Source,
|
||||
"state": p.State,
|
||||
"full_name": p.FullName,
|
||||
"kind": p.Kind,
|
||||
"additional_email_addresses": p.AdditionalEmailAddresses,
|
||||
@@ -850,6 +919,8 @@ func (p *MembershipProfile) Update(
|
||||
UPDATE
|
||||
iam_membership_profiles
|
||||
SET
|
||||
source = @source,
|
||||
state = @state,
|
||||
full_name = @full_name,
|
||||
kind = @kind,
|
||||
additional_email_addresses = @additional_email_addresses,
|
||||
@@ -866,6 +937,8 @@ WHERE
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": p.ID,
|
||||
"source": p.Source,
|
||||
"state": p.State,
|
||||
"full_name": p.FullName,
|
||||
"kind": p.Kind,
|
||||
"additional_email_addresses": p.AdditionalEmailAddresses,
|
||||
@@ -888,6 +961,38 @@ WHERE
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MembershipProfiles) ResetSCIMSources(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE iam_membership_profiles
|
||||
SET
|
||||
source = 'MANUAL',
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND source = 'SCIM'
|
||||
`
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{
|
||||
"organization_id": organizationID,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := conn.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot reset SCIM user sources: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MembershipProfile) Delete(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
|
||||
@@ -18,12 +18,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
)
|
||||
|
||||
type (
|
||||
MembershipProfileFilter struct {
|
||||
excludeContractEnded *bool
|
||||
currentDate time.Time
|
||||
email *mail.Addr
|
||||
state *ProfileState
|
||||
source *ProfileSource
|
||||
}
|
||||
)
|
||||
|
||||
@@ -34,20 +38,72 @@ func NewMembershipProfileFilter(excludeContractEnded *bool) *MembershipProfileFi
|
||||
}
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) WithEmail(email *mail.Addr) *MembershipProfileFilter {
|
||||
f.email = email
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) Email() *mail.Addr {
|
||||
return f.email
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) WithState(state ProfileState) *MembershipProfileFilter {
|
||||
f.state = &state
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) State() *ProfileState {
|
||||
return f.state
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) WithSource(source ProfileSource) *MembershipProfileFilter {
|
||||
f.source = &source
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) Source() *ProfileSource {
|
||||
return f.source
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
return pgx.StrictNamedArgs{
|
||||
"filter_email": f.email,
|
||||
"exclude_contract_ended": f.excludeContractEnded,
|
||||
"current_date": f.currentDate,
|
||||
"filter_state": f.state,
|
||||
"filter_source": f.source,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *MembershipProfileFilter) SQLFragment() string {
|
||||
return `
|
||||
(
|
||||
CASE
|
||||
WHEN @filter_email::text IS NOT NULL THEN
|
||||
i.email_address = @filter_email::text
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @exclude_contract_ended::boolean IS NOT NULL AND @exclude_contract_ended::boolean = true THEN
|
||||
(contract_end_date IS NULL OR contract_end_date >= @current_date::date)
|
||||
(p.contract_end_date IS NULL OR p.contract_end_date >= @current_date::date)
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @filter_state::text IS NOT NULL THEN
|
||||
p.state = @filter_state::membership_state
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
AND (
|
||||
CASE
|
||||
WHEN @filter_source::text IS NOT NULL THEN
|
||||
p.source = @filter_source::text
|
||||
ELSE TRUE
|
||||
END
|
||||
)
|
||||
`
|
||||
}
|
||||
|
||||
@@ -1,2 +1,46 @@
|
||||
ALTER TABLE
|
||||
iam_membership_profiles
|
||||
ADD
|
||||
COLUMN state membership_state NOT NULL DEFAULT 'ACTIVE',
|
||||
ADD
|
||||
COLUMN source TEXT NOT NULL DEFAULT 'MANUAL';
|
||||
|
||||
UPDATE
|
||||
iam_membership_profiles p
|
||||
SET
|
||||
state = m.state,
|
||||
source = m.source
|
||||
FROM
|
||||
iam_memberships m
|
||||
WHERE
|
||||
m.id = p.membership_id;
|
||||
|
||||
ALTER TABLE
|
||||
iam_membership_profiles DROP COLUMN membership_id;
|
||||
|
||||
ALTER TABLE
|
||||
iam_scim_events
|
||||
ADD
|
||||
COLUMN user_name CITEXT NOT NULL DEFAULT '';
|
||||
|
||||
WITH emails AS (
|
||||
SELECT
|
||||
i.email_address,
|
||||
m.id
|
||||
FROM
|
||||
iam_memberships m
|
||||
INNER JOIN identities i ON i.id = m.identity_id
|
||||
)
|
||||
UPDATE
|
||||
iam_scim_events se
|
||||
SET
|
||||
user_name = e.email_address
|
||||
FROM
|
||||
emails e
|
||||
WHERE
|
||||
e.id = se.membership_id;
|
||||
|
||||
ALTER TABLE
|
||||
iam_scim_events
|
||||
ALTER COLUMN
|
||||
user_name DROP DEFAULT;
|
||||
|
||||
@@ -19,19 +19,19 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type MembershipSource string
|
||||
type ProfileSource string
|
||||
|
||||
const (
|
||||
MembershipSourceManual MembershipSource = "MANUAL"
|
||||
MembershipSourceSAML MembershipSource = "SAML"
|
||||
MembershipSourceSCIM MembershipSource = "SCIM"
|
||||
ProfileSourceManual ProfileSource = "MANUAL"
|
||||
ProfileSourceSAML ProfileSource = "SAML"
|
||||
ProfileSourceSCIM ProfileSource = "SCIM"
|
||||
)
|
||||
|
||||
func (s MembershipSource) String() string {
|
||||
func (s ProfileSource) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s *MembershipSource) Scan(value any) error {
|
||||
func (s *ProfileSource) Scan(value any) error {
|
||||
var str string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
@@ -39,22 +39,22 @@ func (s *MembershipSource) Scan(value any) error {
|
||||
case []byte:
|
||||
str = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for MembershipSource: %T", value)
|
||||
return fmt.Errorf("unsupported type for ProfileSource: %T", value)
|
||||
}
|
||||
|
||||
switch str {
|
||||
case "MANUAL":
|
||||
*s = MembershipSourceManual
|
||||
*s = ProfileSourceManual
|
||||
case "SAML":
|
||||
*s = MembershipSourceSAML
|
||||
*s = ProfileSourceSAML
|
||||
case "SCIM":
|
||||
*s = MembershipSourceSCIM
|
||||
*s = ProfileSourceSCIM
|
||||
default:
|
||||
return fmt.Errorf("invalid MembershipSource value: %q", str)
|
||||
return fmt.Errorf("invalid ProfileSource value: %q", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s MembershipSource) Value() (driver.Value, error) {
|
||||
func (s ProfileSource) Value() (driver.Value, error) {
|
||||
return s.String(), nil
|
||||
}
|
||||
@@ -19,18 +19,18 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type MembershipState string
|
||||
type ProfileState string
|
||||
|
||||
const (
|
||||
MembershipStateActive MembershipState = "ACTIVE"
|
||||
MembershipStateInactive MembershipState = "INACTIVE"
|
||||
ProfileStateActive ProfileState = "ACTIVE"
|
||||
ProfileStateInactive ProfileState = "INACTIVE"
|
||||
)
|
||||
|
||||
func (s MembershipState) String() string {
|
||||
func (s ProfileState) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s *MembershipState) Scan(value any) error {
|
||||
func (s *ProfileState) Scan(value any) error {
|
||||
var str string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
@@ -38,20 +38,20 @@ func (s *MembershipState) Scan(value any) error {
|
||||
case []byte:
|
||||
str = string(v)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for MembershipState: %T", value)
|
||||
return fmt.Errorf("unsupported type for ProfileState: %T", value)
|
||||
}
|
||||
|
||||
switch str {
|
||||
case "ACTIVE":
|
||||
*s = MembershipStateActive
|
||||
*s = ProfileStateActive
|
||||
case "INACTIVE":
|
||||
*s = MembershipStateInactive
|
||||
*s = ProfileStateInactive
|
||||
default:
|
||||
return fmt.Errorf("invalid MembershipState value: %q", str)
|
||||
return fmt.Errorf("invalid ProfileState value: %q", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s MembershipState) Value() (driver.Value, error) {
|
||||
func (s ProfileState) Value() (driver.Value, error) {
|
||||
return s.String(), nil
|
||||
}
|
||||
@@ -39,7 +39,7 @@ type (
|
||||
ResponseBody *string `db:"response_body"`
|
||||
StatusCode int `db:"status_code"`
|
||||
ErrorMessage *string `db:"error_message"`
|
||||
MembershipID *gid.GID `db:"membership_id"`
|
||||
UserName string `db:"user_name"`
|
||||
IPAddress net.IP `db:"ip_address"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
@@ -87,7 +87,7 @@ SELECT
|
||||
response_body,
|
||||
status_code,
|
||||
error_message,
|
||||
membership_id,
|
||||
user_name,
|
||||
ip_address,
|
||||
created_at
|
||||
FROM
|
||||
@@ -139,7 +139,7 @@ INSERT INTO iam_scim_events (
|
||||
response_body,
|
||||
status_code,
|
||||
error_message,
|
||||
membership_id,
|
||||
user_name,
|
||||
ip_address,
|
||||
created_at
|
||||
) VALUES (
|
||||
@@ -153,7 +153,7 @@ INSERT INTO iam_scim_events (
|
||||
@response_body,
|
||||
@status_code,
|
||||
@error_message,
|
||||
@membership_id,
|
||||
@user_name,
|
||||
@ip_address,
|
||||
@created_at
|
||||
)
|
||||
@@ -170,7 +170,7 @@ INSERT INTO iam_scim_events (
|
||||
"response_body": s.ResponseBody,
|
||||
"status_code": s.StatusCode,
|
||||
"error_message": s.ErrorMessage,
|
||||
"membership_id": s.MembershipID,
|
||||
"user_name": s.UserName,
|
||||
"ip_address": s.IPAddress,
|
||||
"created_at": s.CreatedAt,
|
||||
}
|
||||
@@ -201,7 +201,7 @@ SELECT
|
||||
response_body,
|
||||
status_code,
|
||||
error_message,
|
||||
membership_id,
|
||||
user_name,
|
||||
ip_address,
|
||||
created_at
|
||||
FROM
|
||||
@@ -281,7 +281,7 @@ SELECT
|
||||
response_body,
|
||||
status_code,
|
||||
error_message,
|
||||
membership_id,
|
||||
user_name,
|
||||
ip_address,
|
||||
created_at
|
||||
FROM
|
||||
|
||||
@@ -188,6 +188,7 @@ func (s *AccountService) AcceptInvitation(
|
||||
) (*coredata.Invitation, *coredata.Membership, error) {
|
||||
var (
|
||||
now = time.Now()
|
||||
profile = &coredata.MembershipProfile{}
|
||||
membership = &coredata.Membership{}
|
||||
invitation = &coredata.Invitation{}
|
||||
)
|
||||
@@ -228,47 +229,24 @@ func (s *AccountService) AcceptInvitation(
|
||||
tenantID := invitation.OrganizationID.TenantID()
|
||||
scope := coredata.NewScope(invitation.OrganizationID.TenantID())
|
||||
|
||||
existingMembership := &coredata.Membership{}
|
||||
if err := existingMembership.LoadByIdentityAndOrg(
|
||||
existingProfile := &coredata.MembershipProfile{}
|
||||
if err := existingProfile.LoadByIdentityIDAndOrganizationID(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
identityID,
|
||||
invitation.OrganizationID,
|
||||
); err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load existing membership: %w", err)
|
||||
}
|
||||
|
||||
if existingMembership.ID != gid.Nil && existingMembership.State == coredata.MembershipStateInactive {
|
||||
existingMembership.State = coredata.MembershipStateActive
|
||||
existingMembership.Role = invitation.Role
|
||||
existingMembership.UpdatedAt = now
|
||||
|
||||
if err := existingMembership.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot reactivate membership: %w", err)
|
||||
); err != nil {
|
||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return fmt.Errorf("cannot load existing profile: %w", err)
|
||||
}
|
||||
|
||||
membership = existingMembership
|
||||
} else {
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||
IdentityID: identityID,
|
||||
OrganizationID: invitation.OrganizationID,
|
||||
Role: invitation.Role,
|
||||
Source: coredata.MembershipSourceManual,
|
||||
State: coredata.MembershipStateActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := membership.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot create membership: %w", err)
|
||||
}
|
||||
|
||||
profile := &coredata.MembershipProfile{
|
||||
profile = &coredata.MembershipProfile{
|
||||
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: invitation.OrganizationID,
|
||||
Source: coredata.ProfileSourceManual,
|
||||
State: coredata.ProfileStateActive,
|
||||
FullName: identity.FullName,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -277,6 +255,51 @@ func (s *AccountService) AcceptInvitation(
|
||||
if err := profile.Insert(ctx, tx); err != nil {
|
||||
return fmt.Errorf("cannot insert profile: %w", err)
|
||||
}
|
||||
} else {
|
||||
if existingProfile.State == coredata.ProfileStateInactive {
|
||||
existingProfile.State = coredata.ProfileStateActive
|
||||
|
||||
if err := existingProfile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot reactivate profile: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
profile = existingProfile
|
||||
}
|
||||
|
||||
existingMembership := &coredata.Membership{}
|
||||
if err := existingMembership.LoadByIdentityAndOrg(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
identityID,
|
||||
invitation.OrganizationID,
|
||||
); err != nil {
|
||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return fmt.Errorf("cannot load existing membership: %w", err)
|
||||
}
|
||||
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||
IdentityID: identityID,
|
||||
OrganizationID: invitation.OrganizationID,
|
||||
Role: invitation.Role,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := membership.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot create membership: %w", err)
|
||||
}
|
||||
} else {
|
||||
existingMembership.Role = invitation.Role
|
||||
existingMembership.UpdatedAt = now
|
||||
|
||||
if err := existingMembership.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot assign membership role: %w", err)
|
||||
}
|
||||
|
||||
membership = existingMembership
|
||||
}
|
||||
|
||||
invitation.AcceptedAt = &now
|
||||
@@ -384,32 +407,6 @@ func (s *AccountService) CountPendingInvitations(
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *AccountService) CountMemberships(
|
||||
ctx context.Context,
|
||||
identityID gid.GID,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
memberships := coredata.Memberships{}
|
||||
count, err = memberships.CountByIdentityID(ctx, conn, identityID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count memberships: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID, req *ChangePasswordRequest) error {
|
||||
if err := req.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid request: %w", err)
|
||||
@@ -857,7 +854,7 @@ func (s *AccountService) ListProfilesForIdentity(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := profiles.LoadByIdentityID(ctx, conn, coredata.NewNoScope(), identityID, cursor, filter); err != nil {
|
||||
if err := profiles.LoadByIdentityID(ctx, conn, identityID, cursor, filter); err != nil {
|
||||
return fmt.Errorf("cannot load profiles: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -81,11 +81,10 @@ func (a *Authorizer) authorize(ctx context.Context, conn pg.Conn, params Authori
|
||||
resourceOrgID := resourceAttrs["organization_id"]
|
||||
|
||||
// Find role for resource's organization
|
||||
memberships, err := a.loadMemberships(ctx, conn, params.Principal)
|
||||
membership, err := a.loadMembership(ctx, conn, params.Principal, resourceOrgID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load memberships for principal: %w", err)
|
||||
}
|
||||
membership := findMembershipForOrg(memberships, resourceOrgID)
|
||||
|
||||
// Check whether the viewer is currently assuming the org of the accessed resource
|
||||
if membership != nil && params.Session != nil {
|
||||
@@ -144,12 +143,31 @@ func (a *Authorizer) authorize(ctx context.Context, conn pg.Conn, params Authori
|
||||
return NewInsufficientPermissionsError(params.Principal, params.Resource, params.Action)
|
||||
}
|
||||
|
||||
func (a *Authorizer) loadMemberships(ctx context.Context, conn pg.Conn, principalID gid.GID) (coredata.Memberships, error) {
|
||||
var memberships coredata.Memberships
|
||||
if err := memberships.LoadAllByIdentityID(ctx, conn, principalID); err != nil {
|
||||
return nil, fmt.Errorf("cannot load memberships: %w", err)
|
||||
func (a *Authorizer) loadMembership(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
principalID gid.GID,
|
||||
resourceOrgID string,
|
||||
) (*coredata.Membership, error) {
|
||||
if resourceOrgID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return memberships, nil
|
||||
|
||||
orgID, err := gid.ParseGID(resourceOrgID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse gid: %w", err)
|
||||
}
|
||||
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadActiveByIdentityIDAndOrganizationID(ctx, conn, principalID, orgID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot load active membership: %w", err)
|
||||
}
|
||||
|
||||
return membership, nil
|
||||
}
|
||||
|
||||
func (a *Authorizer) getActiveChildSessionForMembership(
|
||||
@@ -240,13 +258,3 @@ func (a *Authorizer) buildPoliciesForRole(role string) []*policy.Policy {
|
||||
|
||||
return policies
|
||||
}
|
||||
|
||||
func findMembershipForOrg(memberships coredata.Memberships, orgID string) *coredata.Membership {
|
||||
for _, m := range memberships {
|
||||
if m.OrganizationID.String() == orgID && m.State == coredata.MembershipStateActive {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -123,28 +123,28 @@ func (e ErrMembershipNotFound) Error() string {
|
||||
return fmt.Sprintf("membership %q not found", e.MembershipID)
|
||||
}
|
||||
|
||||
type ErrMembershipInactive struct {
|
||||
MembershipID gid.GID
|
||||
type ErrUserInactive struct {
|
||||
ProfileID gid.GID
|
||||
}
|
||||
|
||||
func NewMembershipInactiveError(membershipID gid.GID) error {
|
||||
return &ErrMembershipInactive{MembershipID: membershipID}
|
||||
func NewUserInactiveError(profileID gid.GID) error {
|
||||
return &ErrUserInactive{ProfileID: profileID}
|
||||
}
|
||||
|
||||
func (e ErrMembershipInactive) Error() string {
|
||||
return fmt.Sprintf("membership %q is inactive", e.MembershipID)
|
||||
func (e ErrUserInactive) Error() string {
|
||||
return fmt.Sprintf("user %q is inactive", e.ProfileID)
|
||||
}
|
||||
|
||||
type ErrMembershipManagedBySCIM struct {
|
||||
MembershipID gid.GID
|
||||
type ErrUserManagedBySCIM struct {
|
||||
ProfileID gid.GID
|
||||
}
|
||||
|
||||
func NewMembershipManagedBySCIMError(membershipID gid.GID) error {
|
||||
return &ErrMembershipManagedBySCIM{MembershipID: membershipID}
|
||||
func NewUserManagedBySCIMError(profileID gid.GID) error {
|
||||
return &ErrUserManagedBySCIM{ProfileID: profileID}
|
||||
}
|
||||
|
||||
func (e ErrMembershipManagedBySCIM) Error() string {
|
||||
return fmt.Sprintf("membership %q is managed by SCIM and cannot be deleted manually", e.MembershipID)
|
||||
func (e ErrUserManagedBySCIM) Error() string {
|
||||
return fmt.Sprintf("user %q is managed by SCIM and cannot be deleted manually", e.ProfileID)
|
||||
}
|
||||
|
||||
type ErrLastActiveOwner struct {
|
||||
@@ -156,7 +156,7 @@ func NewLastActiveOwnerError(membershipID gid.GID) error {
|
||||
}
|
||||
|
||||
func (e ErrLastActiveOwner) Error() string {
|
||||
return fmt.Sprintf("cannot remove membership %q: last active owner of the organization", e.MembershipID)
|
||||
return fmt.Sprintf("cannot remove profile %q: last active owner of the organization", e.MembershipID)
|
||||
}
|
||||
|
||||
type ErrOrganizationNotFound struct{ OrganizationID gid.GID }
|
||||
@@ -220,17 +220,17 @@ func (e ErrSessionExpired) Error() string {
|
||||
return fmt.Sprintf("session %q expired", e.SessionID)
|
||||
}
|
||||
|
||||
type ErrMembershipAlreadyExists struct {
|
||||
type ErrUserAlreadyExists struct {
|
||||
IdentityID gid.GID
|
||||
OrganizationID gid.GID
|
||||
}
|
||||
|
||||
func NewMembershipAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
||||
return &ErrMembershipAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
||||
func NewUserAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
||||
return &ErrUserAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
||||
}
|
||||
|
||||
func (e ErrMembershipAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("membership already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
||||
func (e ErrUserAlreadyExists) Error() string {
|
||||
return fmt.Sprintf("user already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
||||
}
|
||||
|
||||
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
|
||||
|
||||
@@ -212,33 +212,6 @@ func NewOrganizationService(svc *Service) *OrganizationService {
|
||||
return &OrganizationService{Service: svc}
|
||||
}
|
||||
|
||||
func (s *OrganizationService) CountMemberships(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
) (int, error) {
|
||||
var count int
|
||||
scope := coredata.NewScopeFromObjectID(organizationID)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
memberships := coredata.Memberships{}
|
||||
count, err = memberships.CountByOrganizationID(ctx, conn, scope, organizationID, coredata.NewMembershipFilter())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count memberships: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s *OrganizationService) UpdateMempership(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
@@ -282,48 +255,53 @@ func (s *OrganizationService) UpdateMempership(
|
||||
func (s *OrganizationService) RemoveMember(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
membershipID gid.GID,
|
||||
profileID gid.GID,
|
||||
) error {
|
||||
scope := coredata.NewScopeFromObjectID(organizationID)
|
||||
|
||||
return s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
membership := coredata.Membership{}
|
||||
profile := coredata.MembershipProfile{}
|
||||
|
||||
if err := membership.LoadByID(ctx, tx, scope, membershipID); err != nil {
|
||||
if err := profile.LoadByID(ctx, tx, scope, profileID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewMembershipNotFoundError(membershipID)
|
||||
return NewProfileNotFoundError(profileID)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.OrganizationID != organizationID {
|
||||
return NewMembershipNotFoundError(profile.ID)
|
||||
}
|
||||
|
||||
if profile.Source == coredata.ProfileSourceSCIM {
|
||||
return NewUserManagedBySCIMError(profileID)
|
||||
}
|
||||
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadByIdentityAndOrg(ctx, tx, scope, profile.IdentityID, profile.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.OrganizationID != organizationID {
|
||||
return NewMembershipNotFoundError(membership.ID)
|
||||
}
|
||||
|
||||
if membership.Source == coredata.MembershipSourceSCIM {
|
||||
return NewMembershipManagedBySCIMError(membershipID)
|
||||
}
|
||||
|
||||
if membership.Role == coredata.MembershipRoleOwner && membership.State == coredata.MembershipStateActive {
|
||||
memberships := coredata.Memberships{}
|
||||
filter := coredata.NewMembershipFilter().
|
||||
WithRole(coredata.MembershipRoleOwner).
|
||||
WithState(coredata.MembershipStateActive)
|
||||
count, err := memberships.CountByOrganizationID(ctx, tx, scope, organizationID, filter)
|
||||
if membership.Role == coredata.MembershipRoleOwner && profile.State == coredata.ProfileStateActive {
|
||||
profiles := coredata.MembershipProfiles{}
|
||||
count, err := profiles.CountActiveOwnerByOrganizationID(ctx, tx, scope, organizationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count active owners: %w", err)
|
||||
}
|
||||
|
||||
if count <= 1 {
|
||||
return NewLastActiveOwnerError(membershipID)
|
||||
return NewLastActiveOwnerError(profileID)
|
||||
}
|
||||
}
|
||||
|
||||
err := membership.Delete(ctx, tx, scope, membershipID)
|
||||
if err != nil {
|
||||
if err := profile.Delete(ctx, tx, scope, profileID); err != nil {
|
||||
return fmt.Errorf("cannot delete profile: %w", err)
|
||||
}
|
||||
|
||||
if err := membership.Delete(ctx, tx, scope, membership.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete membership: %w", err)
|
||||
}
|
||||
|
||||
@@ -466,14 +444,14 @@ func (s *OrganizationService) InviteMember(
|
||||
|
||||
identityExists := identity.ID != gid.Nil
|
||||
if identityExists {
|
||||
membership := &coredata.Membership{}
|
||||
err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, organizationID)
|
||||
profile := &coredata.MembershipProfile{}
|
||||
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, identity.ID, organizationID)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if membership.ID != gid.Nil && membership.State == coredata.MembershipStateActive {
|
||||
return NewMembershipAlreadyExistsError(identity.ID, organizationID)
|
||||
if profile.ID != gid.Nil && profile.State == coredata.ProfileStateActive {
|
||||
return NewUserAlreadyExistsError(identity.ID, organizationID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,13 +527,22 @@ func (s *OrganizationService) CreateOrganization(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
profile = &coredata.MembershipProfile{
|
||||
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
||||
IdentityID: identityID,
|
||||
OrganizationID: organization.ID,
|
||||
Source: coredata.ProfileSourceManual,
|
||||
State: coredata.ProfileStateActive,
|
||||
FullName: req.Name,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||
IdentityID: identityID,
|
||||
OrganizationID: organizationID,
|
||||
Role: coredata.MembershipRoleOwner,
|
||||
Source: coredata.MembershipSourceManual,
|
||||
State: coredata.MembershipStateActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -688,25 +675,16 @@ func (s *OrganizationService) CreateOrganization(
|
||||
organization.HorizontalLogoFileID = &horizontalLogoFile.ID
|
||||
}
|
||||
|
||||
err = membership.Insert(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create membership: %w", err)
|
||||
}
|
||||
|
||||
profile := &coredata.MembershipProfile{
|
||||
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: organization.ID,
|
||||
FullName: identity.FullName,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = profile.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert profile: %w", err)
|
||||
}
|
||||
|
||||
err = membership.Insert(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert membership: %w", err)
|
||||
}
|
||||
|
||||
if err := organizationContext.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert organization context: %w", err)
|
||||
}
|
||||
@@ -1508,10 +1486,10 @@ func (s OrganizationService) DeleteSCIMConfiguration(
|
||||
return scim.NewSCIMConfigurationNotFoundError(configID)
|
||||
}
|
||||
|
||||
memberships := &coredata.Memberships{}
|
||||
err = memberships.ResetSCIMSources(ctx, tx, scope, config.OrganizationID)
|
||||
profiles := &coredata.MembershipProfiles{}
|
||||
err = profiles.ResetSCIMSources(ctx, tx, scope, config.OrganizationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot reset membership sources: %w", err)
|
||||
return fmt.Errorf("cannot reset user sources: %w", err)
|
||||
}
|
||||
|
||||
// Delete SCIM bridge and its connector if they exist
|
||||
|
||||
@@ -89,12 +89,12 @@ func (e ErrSAMLAutoSignupDisabled) Error() string {
|
||||
return fmt.Sprintf("SAML auto-signup is disabled for configuration %q", e.ConfigID)
|
||||
}
|
||||
|
||||
type ErrMembershipInactive struct{ MembershipID gid.GID }
|
||||
type ErrUserInactive struct{ ProfileID gid.GID }
|
||||
|
||||
func NewMembershipInactiveError(membershipID gid.GID) error {
|
||||
return &ErrMembershipInactive{MembershipID: membershipID}
|
||||
func NewUserInactiveError(profileID gid.GID) error {
|
||||
return &ErrUserInactive{ProfileID: profileID}
|
||||
}
|
||||
|
||||
func (e ErrMembershipInactive) Error() string {
|
||||
return fmt.Sprintf("membership %q is inactive", e.MembershipID)
|
||||
func (e ErrUserInactive) Error() string {
|
||||
return fmt.Sprintf("user %q is inactive", e.ProfileID)
|
||||
}
|
||||
|
||||
@@ -296,16 +296,23 @@ func (s *Service) HandleAssertion(
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
|
||||
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, identity.ID, config.OrganizationID)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
if err := profile.LoadByIdentityIDAndOrganizationID(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
identity.ID,
|
||||
config.OrganizationID,
|
||||
); err != nil {
|
||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.ID == gid.Nil {
|
||||
profile = &coredata.MembershipProfile{
|
||||
ID: gid.New(membership.ID.TenantID(), coredata.MembershipProfileEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Source: coredata.ProfileSourceSAML,
|
||||
State: coredata.ProfileStateActive,
|
||||
FullName: fullname,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -315,25 +322,28 @@ func (s *Service) HandleAssertion(
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert membership profile: %w", err)
|
||||
}
|
||||
} else {
|
||||
if profile.State == coredata.ProfileStateInactive {
|
||||
return NewUserInactiveError(membership.ID)
|
||||
}
|
||||
}
|
||||
|
||||
err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, config.OrganizationID)
|
||||
if err != nil && err != coredata.ErrResourceNotFound {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
if err := membership.LoadByIdentityAndOrg(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
identity.ID,
|
||||
config.OrganizationID,
|
||||
); err != nil {
|
||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.ID != gid.Nil && membership.State == coredata.MembershipStateInactive {
|
||||
return NewMembershipInactiveError(membership.ID)
|
||||
}
|
||||
|
||||
if membership.ID == gid.Nil {
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Role: coredata.MembershipRoleEmployee,
|
||||
Source: coredata.MembershipSourceSAML,
|
||||
State: coredata.MembershipStateActive,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
@@ -358,34 +368,26 @@ func (s *Service) HandleAssertion(
|
||||
}
|
||||
}
|
||||
|
||||
if membership.Source != coredata.MembershipSourceSCIM {
|
||||
needsUpdate := false
|
||||
if profile.Source != coredata.ProfileSourceSCIM {
|
||||
profile.FullName = fullname
|
||||
profile.UpdatedAt = now
|
||||
if profile.Source == coredata.ProfileSourceManual {
|
||||
profile.Source = coredata.ProfileSourceSAML
|
||||
}
|
||||
err = profile.Update(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update profile: %w", err)
|
||||
}
|
||||
|
||||
if role != nil {
|
||||
membership.Role = *role
|
||||
membership.UpdatedAt = now
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if membership.Source == coredata.MembershipSourceManual {
|
||||
membership.Source = coredata.MembershipSourceSAML
|
||||
membership.UpdatedAt = now
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
err = membership.Update(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update membership: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
profile.FullName = fullname
|
||||
profile.UpdatedAt = now
|
||||
err = profile.Update(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update profile: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
)
|
||||
|
||||
func ParseUserFilter(expr scimfilter.Expression) (*coredata.MembershipFilter, error) {
|
||||
filter := coredata.NewMembershipFilter()
|
||||
func ParseUserFilter(expr scimfilter.Expression) (*coredata.MembershipProfileFilter, error) {
|
||||
filter := coredata.NewMembershipProfileFilter(nil)
|
||||
|
||||
if expr == nil {
|
||||
return filter, nil
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -138,103 +139,120 @@ func (s *Service) CreateUser(
|
||||
}
|
||||
now := time.Now()
|
||||
|
||||
membershipState := coredata.MembershipStateActive
|
||||
profileState := coredata.ProfileStateActive
|
||||
if !active {
|
||||
membershipState = coredata.MembershipStateInactive
|
||||
profileState = coredata.ProfileStateInactive
|
||||
}
|
||||
|
||||
var membership *coredata.Membership
|
||||
var profile *coredata.MembershipProfile
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
|
||||
err = s.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||
// Check if identity exists
|
||||
identity := &coredata.Identity{}
|
||||
err := identity.LoadByEmail(ctx, tx, emailAddr)
|
||||
if err := identity.LoadByEmail(ctx, tx, emailAddr); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
// Create new identity
|
||||
identity = &coredata.Identity{
|
||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
||||
EmailAddress: emailAddr,
|
||||
FullName: fullName,
|
||||
HashedPassword: nil,
|
||||
EmailAddressVerified: false,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
// Create new identity
|
||||
identity = &coredata.Identity{
|
||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
||||
EmailAddress: emailAddr,
|
||||
FullName: fullName,
|
||||
HashedPassword: nil,
|
||||
EmailAddressVerified: false,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
err = identity.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert identity: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = identity.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert identity: %w", err)
|
||||
// Check if profile exists
|
||||
profile = &coredata.MembershipProfile{}
|
||||
if err := profile.LoadByIdentityIDAndOrganizationID(
|
||||
ctx,
|
||||
tx,
|
||||
coredata.NewScopeFromObjectID(config.OrganizationID),
|
||||
identity.ID,
|
||||
config.OrganizationID,
|
||||
); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
profile = &coredata.MembershipProfile{
|
||||
ID: gid.New(config.OrganizationID.TenantID(), coredata.MembershipProfileEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Source: coredata.ProfileSourceSCIM,
|
||||
State: profileState,
|
||||
FullName: fullName,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = profile.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert profile: %w", err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
} else {
|
||||
profile.Source = coredata.ProfileSourceSCIM
|
||||
profile.State = profileState
|
||||
profile.UpdatedAt = now
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update profile: %w", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
// Check if membership exists
|
||||
membership = &coredata.Membership{}
|
||||
err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, config.OrganizationID)
|
||||
if err := membership.LoadByIdentityAndOrg(
|
||||
ctx,
|
||||
tx,
|
||||
scope,
|
||||
identity.ID,
|
||||
config.OrganizationID,
|
||||
); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
// Create new membership
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(config.OrganizationID.TenantID(), coredata.MembershipEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Role: coredata.MembershipRoleEmployee,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
// Create new membership
|
||||
membership = &coredata.Membership{
|
||||
ID: gid.New(config.OrganizationID.TenantID(), coredata.MembershipEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
Role: coredata.MembershipRoleEmployee,
|
||||
Source: coredata.MembershipSourceSCIM,
|
||||
State: membershipState,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
err = membership.Insert(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert membership: %w", err)
|
||||
}
|
||||
|
||||
err = membership.Insert(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert membership: %w", err)
|
||||
}
|
||||
// Expire all pending invitations for email in organization
|
||||
invitations := &coredata.Invitations{}
|
||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||
err := invitations.ExpireByEmailAndOrganization(
|
||||
ctx,
|
||||
tx,
|
||||
coredata.NewScopeFromObjectID(config.OrganizationID),
|
||||
emailAddr,
|
||||
config.OrganizationID,
|
||||
onlyPending,
|
||||
)
|
||||
|
||||
// Create membership profile
|
||||
membershipProfile := &coredata.MembershipProfile{
|
||||
ID: gid.New(membership.ID.TenantID(), coredata.MembershipProfileEntityType),
|
||||
IdentityID: identity.ID,
|
||||
OrganizationID: config.OrganizationID,
|
||||
FullName: fullName,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err = membershipProfile.Insert(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert membership profile: %w", err)
|
||||
}
|
||||
|
||||
// Expire all pending invitations for email in organization
|
||||
invitations := &coredata.Invitations{}
|
||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||
err := invitations.ExpireByEmailAndOrganization(
|
||||
ctx,
|
||||
tx,
|
||||
coredata.NewScopeFromObjectID(config.OrganizationID),
|
||||
emailAddr,
|
||||
config.OrganizationID,
|
||||
onlyPending,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot expire pending invitations by email")
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
} else {
|
||||
// Update existing membership - follow what SCIM tells us
|
||||
membership.Source = coredata.MembershipSourceSCIM
|
||||
membership.State = membershipState
|
||||
membership.UpdatedAt = now
|
||||
|
||||
err = membership.Update(ctx, tx, scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update membership: %w", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot expire pending invitations by email")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,32 +263,45 @@ func (s *Service) CreateUser(
|
||||
return scim.Resource{}, err
|
||||
}
|
||||
|
||||
return membershipToResource(membership), nil
|
||||
return userToResource(profile), nil
|
||||
}
|
||||
|
||||
func (s *Service) GetUser(
|
||||
ctx context.Context,
|
||||
config *coredata.SCIMConfiguration,
|
||||
membershipID gid.GID,
|
||||
profileID gid.GID,
|
||||
) (scim.Resource, error) {
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
|
||||
var membership *coredata.Membership
|
||||
var (
|
||||
profile *coredata.MembershipProfile
|
||||
membership *coredata.Membership
|
||||
)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
membership = &coredata.Membership{}
|
||||
err := membership.LoadByID(ctx, conn, scope, membershipID)
|
||||
if err != nil {
|
||||
profile = &coredata.MembershipProfile{}
|
||||
if err := profile.LoadByID(ctx, conn, scope, profileID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
||||
return scimerrors.ScimErrorResourceNotFound(profileID.String())
|
||||
}
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.OrganizationID != config.OrganizationID {
|
||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
||||
if profile.OrganizationID != config.OrganizationID {
|
||||
return scimerrors.ScimErrorResourceNotFound(profileID.String())
|
||||
}
|
||||
|
||||
membership = &coredata.Membership{}
|
||||
if err := membership.LoadByIdentityAndOrg(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
profile.IdentityID,
|
||||
profile.OrganizationID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -281,7 +312,7 @@ func (s *Service) GetUser(
|
||||
return scim.Resource{}, err
|
||||
}
|
||||
|
||||
return membershipToResource(membership), nil
|
||||
return userToResource(profile), nil
|
||||
}
|
||||
|
||||
func (s *Service) ListUsers(
|
||||
@@ -301,31 +332,31 @@ func (s *Service) ListUsers(
|
||||
// when they don't exist in the identity provider.
|
||||
// 2. When a manual user exists in the identity provider but not in the
|
||||
// SCIM list, CreateUser is called which enrolls them into SCIM management.
|
||||
filter.WithSource(coredata.MembershipSourceSCIM)
|
||||
filter.WithSource(coredata.ProfileSourceSCIM)
|
||||
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
|
||||
var memberships coredata.Memberships
|
||||
var profiles coredata.MembershipProfiles
|
||||
var totalCount int
|
||||
|
||||
err = s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
var err error
|
||||
totalCount, err = memberships.CountByOrganizationID(ctx, conn, scope, config.OrganizationID, filter)
|
||||
totalCount, err = profiles.CountByOrganizationID(ctx, conn, scope, config.OrganizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count memberships: %w", err)
|
||||
return fmt.Errorf("cannot count profiles: %w", err)
|
||||
}
|
||||
|
||||
orderBy := page.OrderBy[coredata.MembershipOrderField]{
|
||||
Field: coredata.MembershipOrderFieldCreatedAt,
|
||||
orderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||
Field: coredata.MembershipProfileOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
cursor := page.NewCursor(count, nil, page.Head, orderBy)
|
||||
|
||||
err = memberships.LoadByOrganizationID(ctx, conn, scope, config.OrganizationID, cursor, filter)
|
||||
err = profiles.LoadByOrganizationID(ctx, conn, scope, config.OrganizationID, cursor, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load memberships: %w", err)
|
||||
return fmt.Errorf("cannot load profiles: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -336,9 +367,9 @@ func (s *Service) ListUsers(
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
resources := make([]scim.Resource, 0, len(memberships))
|
||||
for _, m := range memberships {
|
||||
resources = append(resources, membershipToResource(m))
|
||||
resources := make([]scim.Resource, 0, len(profiles))
|
||||
for _, p := range profiles {
|
||||
resources = append(resources, userToResource(p))
|
||||
}
|
||||
|
||||
return resources, totalCount, nil
|
||||
@@ -347,61 +378,95 @@ func (s *Service) ListUsers(
|
||||
func (s *Service) ReplaceUser(
|
||||
ctx context.Context,
|
||||
config *coredata.SCIMConfiguration,
|
||||
membershipID gid.GID,
|
||||
profileID gid.GID,
|
||||
attributes scim.ResourceAttributes,
|
||||
) (scim.Resource, error) {
|
||||
fullName, active := ParseUserFromReplaceAttributes(attributes)
|
||||
membership, err := s.updateUser(ctx, config, membershipID, fullName, active)
|
||||
profile, err := s.updateUser(ctx, config, profileID, fullName, active)
|
||||
if err != nil {
|
||||
return scim.Resource{}, err
|
||||
}
|
||||
|
||||
return membershipToResource(membership), nil
|
||||
return userToResource(profile), nil
|
||||
}
|
||||
|
||||
func (s *Service) PatchUser(
|
||||
ctx context.Context,
|
||||
config *coredata.SCIMConfiguration,
|
||||
membershipID gid.GID,
|
||||
profileID gid.GID,
|
||||
operations []scim.PatchOperation,
|
||||
) (scim.Resource, error) {
|
||||
fullName, active := ParseUserFromPatchOperations(operations)
|
||||
membership, err := s.updateUser(ctx, config, membershipID, fullName, active)
|
||||
profile, err := s.updateUser(ctx, config, profileID, fullName, active)
|
||||
if err != nil {
|
||||
return scim.Resource{}, err
|
||||
}
|
||||
|
||||
return membershipToResource(membership), nil
|
||||
return userToResource(profile), nil
|
||||
}
|
||||
|
||||
func (s *Service) updateUser(
|
||||
ctx context.Context,
|
||||
config *coredata.SCIMConfiguration,
|
||||
membershipID gid.GID,
|
||||
profileID gid.GID,
|
||||
fullName string,
|
||||
active *bool,
|
||||
) (*coredata.Membership, error) {
|
||||
) (*coredata.MembershipProfile, error) {
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
now := time.Now()
|
||||
|
||||
var membership *coredata.Membership
|
||||
var (
|
||||
membership *coredata.Membership
|
||||
profile *coredata.MembershipProfile
|
||||
)
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
membership = &coredata.Membership{}
|
||||
if err := membership.LoadByID(ctx, tx, scope, membershipID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
||||
profile = &coredata.MembershipProfile{}
|
||||
if err := profile.LoadByID(ctx, tx, scope, profileID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return scimerrors.ScimErrorResourceNotFound(profileID.String())
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.OrganizationID != config.OrganizationID {
|
||||
return scimerrors.ScimErrorResourceNotFound(profileID.String())
|
||||
}
|
||||
|
||||
membership = &coredata.Membership{}
|
||||
if err := membership.LoadByIdentityAndOrg(ctx, tx, scope, profile.IdentityID, profile.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.OrganizationID != config.OrganizationID {
|
||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
||||
shouldReactivate := active != nil && *active && profile.State == coredata.ProfileStateInactive
|
||||
shouldDeactivate := active != nil && !*active && profile.State == coredata.ProfileStateActive
|
||||
|
||||
if fullName != "" {
|
||||
profile.FullName = fullName
|
||||
profile.UpdatedAt = now
|
||||
}
|
||||
|
||||
needsUpdate := false
|
||||
if shouldReactivate {
|
||||
profile.State = coredata.ProfileStateActive
|
||||
profile.UpdatedAt = now
|
||||
} else if shouldDeactivate {
|
||||
profile.State = coredata.ProfileStateInactive
|
||||
profile.UpdatedAt = now
|
||||
}
|
||||
|
||||
if profile.Source != coredata.ProfileSourceSCIM {
|
||||
profile.Source = coredata.ProfileSourceSCIM
|
||||
profile.UpdatedAt = now
|
||||
}
|
||||
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update membership profile: %w", err)
|
||||
}
|
||||
|
||||
needsUpdate := shouldReactivate || shouldDeactivate
|
||||
|
||||
if active != nil {
|
||||
identity := &coredata.Identity{}
|
||||
@@ -409,11 +474,8 @@ func (s *Service) updateUser(
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
if *active && membership.State == coredata.MembershipStateInactive {
|
||||
membership.State = coredata.MembershipStateActive
|
||||
if shouldReactivate {
|
||||
membership.Role = coredata.MembershipRoleEmployee
|
||||
needsUpdate = true
|
||||
|
||||
// Expire all pending invitations for email in organization
|
||||
invitations := &coredata.Invitations{}
|
||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||
@@ -427,10 +489,7 @@ func (s *Service) updateUser(
|
||||
); err != nil {
|
||||
return fmt.Errorf("cannot expire pending invitations by email: %w", err)
|
||||
}
|
||||
} else if !*active && membership.State == coredata.MembershipStateActive {
|
||||
membership.State = coredata.MembershipStateInactive
|
||||
needsUpdate = true
|
||||
|
||||
} else if shouldDeactivate {
|
||||
// Expire all pending invitations for email in organization
|
||||
invitations := &coredata.Invitations{}
|
||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||
@@ -447,11 +506,6 @@ func (s *Service) updateUser(
|
||||
}
|
||||
}
|
||||
|
||||
if membership.Source != coredata.MembershipSourceSCIM {
|
||||
membership.Source = coredata.MembershipSourceSCIM
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
membership.UpdatedAt = now
|
||||
if err := membership.Update(ctx, tx, scope); err != nil {
|
||||
@@ -459,18 +513,6 @@ func (s *Service) updateUser(
|
||||
}
|
||||
}
|
||||
|
||||
profile := &coredata.MembershipProfile{}
|
||||
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, membership.IdentityID, membership.OrganizationID); err == nil {
|
||||
if fullName != "" {
|
||||
profile.FullName = fullName
|
||||
profile.UpdatedAt = now
|
||||
|
||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update membership profile: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
@@ -479,7 +521,7 @@ func (s *Service) updateUser(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return membership, nil
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteUser(
|
||||
@@ -538,19 +580,14 @@ func (s *Service) LogEvent(
|
||||
config *coredata.SCIMConfiguration,
|
||||
method string,
|
||||
path string,
|
||||
membershipID *gid.GID,
|
||||
userName string,
|
||||
ipAddress net.IP,
|
||||
statusCode int,
|
||||
errorMessage *string,
|
||||
) {
|
||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||
|
||||
var mID gid.GID
|
||||
if membershipID != nil {
|
||||
mID = *membershipID
|
||||
}
|
||||
|
||||
event := s.createEvent(config, method, path, mID, ipAddress, statusCode, errorMessage)
|
||||
event := s.createEvent(config, method, path, userName, ipAddress, statusCode, errorMessage)
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
@@ -572,7 +609,7 @@ func (s *Service) createEvent(
|
||||
config *coredata.SCIMConfiguration,
|
||||
method string,
|
||||
path string,
|
||||
membershipID gid.GID,
|
||||
userName string,
|
||||
ipAddress net.IP,
|
||||
statusCode int,
|
||||
errorMessage *string,
|
||||
@@ -586,13 +623,10 @@ func (s *Service) createEvent(
|
||||
StatusCode: statusCode,
|
||||
ErrorMessage: errorMessage,
|
||||
IPAddress: ipAddress,
|
||||
UserName: userName,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if membershipID != gid.Nil {
|
||||
event.MembershipID = &membershipID
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
@@ -730,28 +764,28 @@ func ParseUserFromPatchOperations(operations []scim.PatchOperation) (fullName st
|
||||
return fullName, active
|
||||
}
|
||||
|
||||
func membershipToResource(m *coredata.Membership) scim.Resource {
|
||||
func userToResource(p *coredata.MembershipProfile) scim.Resource {
|
||||
return scim.Resource{
|
||||
ID: m.ID.String(),
|
||||
ExternalID: optional.NewString(m.ID.String()),
|
||||
ID: p.ID.String(),
|
||||
ExternalID: optional.NewString(p.ID.String()),
|
||||
Attributes: scim.ResourceAttributes{
|
||||
"userName": m.EmailAddress.String(),
|
||||
"displayName": m.FullName,
|
||||
"active": m.State == coredata.MembershipStateActive,
|
||||
"userName": p.EmailAddress.String(),
|
||||
"displayName": p.FullName,
|
||||
"active": p.State == coredata.ProfileStateActive,
|
||||
"name": map[string]any{
|
||||
"formatted": m.FullName,
|
||||
"formatted": p.FullName,
|
||||
},
|
||||
"emails": []map[string]any{
|
||||
{
|
||||
"value": m.EmailAddress.String(),
|
||||
"value": p.EmailAddress.String(),
|
||||
"type": "work",
|
||||
"primary": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Meta: scim.Meta{
|
||||
Created: &m.CreatedAt,
|
||||
LastModified: &m.UpdatedAt,
|
||||
Created: &p.CreatedAt,
|
||||
LastModified: &p.UpdatedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +324,7 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
||||
now = time.Now()
|
||||
rootSession = &coredata.Session{}
|
||||
identity = &coredata.Identity{}
|
||||
profile = &coredata.MembershipProfile{}
|
||||
membership = &coredata.Membership{}
|
||||
childSession = &coredata.Session{}
|
||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||
@@ -353,6 +354,18 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, rootSession.IdentityID, organizationID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewProfileNotFoundError(organizationID)
|
||||
}
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.State == coredata.ProfileStateInactive {
|
||||
return NewUserInactiveError(profile.ID)
|
||||
}
|
||||
|
||||
err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
@@ -361,10 +374,6 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.State == coredata.MembershipStateInactive {
|
||||
return NewMembershipInactiveError(membership.ID)
|
||||
}
|
||||
|
||||
tenantID := scope.GetTenantID()
|
||||
childSession = &coredata.Session{
|
||||
ID: gid.New(tenantID, coredata.SessionEntityType),
|
||||
@@ -417,6 +426,7 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
||||
now = time.Now()
|
||||
rootSession = &coredata.Session{}
|
||||
identity = &coredata.Identity{}
|
||||
profile = &coredata.MembershipProfile{}
|
||||
membership = &coredata.Membership{}
|
||||
childSession = &coredata.Session{}
|
||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||
@@ -446,6 +456,18 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, rootSession.IdentityID, organizationID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewProfileNotFoundError(organizationID)
|
||||
}
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.State == coredata.ProfileStateInactive {
|
||||
return NewUserInactiveError(profile.ID)
|
||||
}
|
||||
|
||||
err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
|
||||
if err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
@@ -454,10 +476,6 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.State == coredata.MembershipStateInactive {
|
||||
return NewMembershipInactiveError(membership.ID)
|
||||
}
|
||||
|
||||
tenantID := scope.GetTenantID()
|
||||
childSession = &coredata.Session{
|
||||
ID: gid.New(tenantID, coredata.SessionEntityType),
|
||||
@@ -497,6 +515,7 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
now = time.Now()
|
||||
rootSession = &coredata.Session{}
|
||||
identity = &coredata.Identity{}
|
||||
profile = &coredata.MembershipProfile{}
|
||||
membership = &coredata.Membership{}
|
||||
childSession = &coredata.Session{}
|
||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||
@@ -524,6 +543,17 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
return fmt.Errorf("cannot load identity: %w", err)
|
||||
}
|
||||
|
||||
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, rootSession.IdentityID, organizationID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewProfileNotFoundError(organizationID)
|
||||
}
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if profile.State == coredata.ProfileStateInactive {
|
||||
return NewUserInactiveError(profile.ID)
|
||||
}
|
||||
|
||||
if err := membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID); err != nil {
|
||||
if err == coredata.ErrResourceNotFound {
|
||||
return NewMembershipNotFoundError(organizationID)
|
||||
@@ -531,10 +561,6 @@ func (s SessionService) AssumeOrganizationSession(
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if membership.State == coredata.MembershipStateInactive {
|
||||
return NewMembershipInactiveError(membership.ID)
|
||||
}
|
||||
|
||||
samlConfig := &coredata.SAMLConfiguration{}
|
||||
err := samlConfig.LoadByOrganizationIDAndEmailDomain(
|
||||
ctx,
|
||||
|
||||
@@ -181,6 +181,8 @@ type Identity implements Node {
|
||||
type Profile implements Node {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
source: String!
|
||||
state: ProfileState!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: ProfileKind!
|
||||
position: String
|
||||
@@ -198,6 +200,32 @@ type Profile implements Node {
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
||||
INACTIVE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive")
|
||||
}
|
||||
|
||||
enum ProfileSource
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileSource") {
|
||||
MANUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceManual")
|
||||
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSAML")
|
||||
SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSCIM")
|
||||
}
|
||||
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
@@ -243,17 +271,6 @@ type Organization implements Node {
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
enum MembershipRole
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
||||
@@ -265,27 +282,10 @@ enum MembershipRole
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor")
|
||||
}
|
||||
|
||||
enum MembershipSource
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipSource") {
|
||||
MANUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceManual")
|
||||
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceSAML")
|
||||
SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceSCIM")
|
||||
}
|
||||
|
||||
enum MembershipState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipState") {
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipStateActive")
|
||||
INACTIVE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipStateInactive")
|
||||
}
|
||||
|
||||
type Membership implements Node {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
role: MembershipRole!
|
||||
source: MembershipSource!
|
||||
state: MembershipState!
|
||||
|
||||
lastSession: Session @goField(forceResolver: true)
|
||||
|
||||
@@ -440,7 +440,7 @@ type SCIMEvent implements Node {
|
||||
requestBody: String
|
||||
responseBody: String
|
||||
errorMessage: String
|
||||
profile: Profile @goField(forceResolver: true)
|
||||
userName: String!
|
||||
ipAddress: String!
|
||||
createdAt: Datetime!
|
||||
|
||||
|
||||
@@ -192,8 +192,6 @@ type ComplexityRoot struct {
|
||||
LastSession func(childComplexity int) int
|
||||
Permission func(childComplexity int, action string) int
|
||||
Role func(childComplexity int) int
|
||||
Source func(childComplexity int) int
|
||||
State func(childComplexity int) int
|
||||
}
|
||||
|
||||
Mutation struct {
|
||||
@@ -299,6 +297,8 @@ type ComplexityRoot struct {
|
||||
Organization func(childComplexity int) int
|
||||
Permission func(childComplexity int, action string) int
|
||||
Position func(childComplexity int) int
|
||||
Source func(childComplexity int) int
|
||||
State func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
|
||||
@@ -414,10 +414,10 @@ type ComplexityRoot struct {
|
||||
Method func(childComplexity int) int
|
||||
Path func(childComplexity int) int
|
||||
Permission func(childComplexity int, action string) int
|
||||
Profile func(childComplexity int) int
|
||||
RequestBody func(childComplexity int) int
|
||||
ResponseBody func(childComplexity int) int
|
||||
StatusCode func(childComplexity int) int
|
||||
UserName func(childComplexity int) int
|
||||
}
|
||||
|
||||
SCIMEventConnection struct {
|
||||
@@ -605,8 +605,6 @@ type SCIMConfigurationResolver interface {
|
||||
Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error)
|
||||
}
|
||||
type SCIMEventResolver interface {
|
||||
Profile(ctx context.Context, obj *types.SCIMEvent) (*types.Profile, error)
|
||||
|
||||
Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error)
|
||||
}
|
||||
type SCIMEventConnectionResolver interface {
|
||||
@@ -1042,18 +1040,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Membership.Role(childComplexity), true
|
||||
case "Membership.source":
|
||||
if e.complexity.Membership.Source == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Membership.Source(childComplexity), true
|
||||
case "Membership.state":
|
||||
if e.complexity.Membership.State == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Membership.State(childComplexity), true
|
||||
|
||||
case "Mutation.acceptInvitation":
|
||||
if e.complexity.Mutation.AcceptInvitation == nil {
|
||||
@@ -1706,6 +1692,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Profile.Position(childComplexity), true
|
||||
case "Profile.source":
|
||||
if e.complexity.Profile.Source == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Profile.Source(childComplexity), true
|
||||
case "Profile.state":
|
||||
if e.complexity.Profile.State == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Profile.State(childComplexity), true
|
||||
case "Profile.updatedAt":
|
||||
if e.complexity.Profile.UpdatedAt == nil {
|
||||
break
|
||||
@@ -2142,12 +2140,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.SCIMEvent.Permission(childComplexity, args["action"].(string)), true
|
||||
case "SCIMEvent.profile":
|
||||
if e.complexity.SCIMEvent.Profile == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.SCIMEvent.Profile(childComplexity), true
|
||||
case "SCIMEvent.requestBody":
|
||||
if e.complexity.SCIMEvent.RequestBody == nil {
|
||||
break
|
||||
@@ -2166,6 +2158,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.SCIMEvent.StatusCode(childComplexity), true
|
||||
case "SCIMEvent.userName":
|
||||
if e.complexity.SCIMEvent.UserName == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.SCIMEvent.UserName(childComplexity), true
|
||||
|
||||
case "SCIMEventConnection.edges":
|
||||
if e.complexity.SCIMEventConnection.Edges == nil {
|
||||
@@ -2683,6 +2681,8 @@ type Identity implements Node {
|
||||
type Profile implements Node {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
source: String!
|
||||
state: ProfileState!
|
||||
additionalEmailAddresses: [EmailAddr!]!
|
||||
kind: ProfileKind!
|
||||
position: String
|
||||
@@ -2700,6 +2700,32 @@ type Profile implements Node {
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
||||
INACTIVE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive")
|
||||
}
|
||||
|
||||
enum ProfileSource
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileSource") {
|
||||
MANUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceManual")
|
||||
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSAML")
|
||||
SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileSourceSCIM")
|
||||
}
|
||||
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
@@ -2745,17 +2771,6 @@ type Organization implements Node {
|
||||
@session(required: PRESENT)
|
||||
}
|
||||
|
||||
enum ProfileKind @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipProfileKind") {
|
||||
EMPLOYEE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindEmployee")
|
||||
CONTRACTOR
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindContractor")
|
||||
SERVICE_ACCOUNT
|
||||
@goEnum(
|
||||
value: "go.probo.inc/probo/pkg/coredata.MembershipProfileKindServiceAccount"
|
||||
)
|
||||
}
|
||||
|
||||
enum MembershipRole
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
||||
@@ -2767,27 +2782,10 @@ enum MembershipRole
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAuditor")
|
||||
}
|
||||
|
||||
enum MembershipSource
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipSource") {
|
||||
MANUAL
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceManual")
|
||||
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceSAML")
|
||||
SCIM @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipSourceSCIM")
|
||||
}
|
||||
|
||||
enum MembershipState
|
||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipState") {
|
||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipStateActive")
|
||||
INACTIVE
|
||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipStateInactive")
|
||||
}
|
||||
|
||||
type Membership implements Node {
|
||||
id: ID!
|
||||
createdAt: Datetime!
|
||||
role: MembershipRole!
|
||||
source: MembershipSource!
|
||||
state: MembershipState!
|
||||
|
||||
lastSession: Session @goField(forceResolver: true)
|
||||
|
||||
@@ -2942,7 +2940,7 @@ type SCIMEvent implements Node {
|
||||
requestBody: String
|
||||
responseBody: String
|
||||
errorMessage: String
|
||||
profile: Profile @goField(forceResolver: true)
|
||||
userName: String!
|
||||
ipAddress: String!
|
||||
createdAt: Datetime!
|
||||
|
||||
@@ -4335,10 +4333,6 @@ func (ec *executionContext) fieldContext_AcceptInvitationPayload_membership(_ co
|
||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||
case "role":
|
||||
return ec.fieldContext_Membership_role(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Membership_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Membership_state(ctx, field)
|
||||
case "lastSession":
|
||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||
case "permission":
|
||||
@@ -4756,10 +4750,6 @@ func (ec *executionContext) fieldContext_CreateOrganizationPayload_membership(_
|
||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||
case "role":
|
||||
return ec.fieldContext_Membership_role(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Membership_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Membership_state(ctx, field)
|
||||
case "lastSession":
|
||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||
case "permission":
|
||||
@@ -6338,64 +6328,6 @@ func (ec *executionContext) fieldContext_Membership_role(_ context.Context, fiel
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Membership_source(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Membership_source,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Source, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Membership_source(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Membership",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type MembershipSource does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Membership_state(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Membership_state,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.State, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Membership_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Membership",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type MembershipState does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Membership_lastSession(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -8921,6 +8853,10 @@ func (ec *executionContext) fieldContext_Organization_viewer(_ context.Context,
|
||||
return ec.fieldContext_Profile_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Profile_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Profile_state(ctx, field)
|
||||
case "additionalEmailAddresses":
|
||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||
case "kind":
|
||||
@@ -9086,10 +9022,6 @@ func (ec *executionContext) fieldContext_OrganizationSessionCreated_membership(_
|
||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||
case "role":
|
||||
return ec.fieldContext_Membership_role(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Membership_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Membership_state(ctx, field)
|
||||
case "lastSession":
|
||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||
case "permission":
|
||||
@@ -9714,6 +9646,64 @@ func (ec *executionContext) fieldContext_Profile_fullName(_ context.Context, fie
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Profile_source(ctx context.Context, field graphql.CollectedField, obj *types.Profile) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Profile_source,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Source, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Profile_source(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Profile",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Profile_state(ctx context.Context, field graphql.CollectedField, obj *types.Profile) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Profile_state,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.State, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState,
|
||||
true,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Profile_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Profile",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type ProfileState does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Profile_additionalEmailAddresses(ctx context.Context, field graphql.CollectedField, obj *types.Profile) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -10063,10 +10053,6 @@ func (ec *executionContext) fieldContext_Profile_membership(_ context.Context, f
|
||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||
case "role":
|
||||
return ec.fieldContext_Membership_role(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Membership_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Membership_state(ctx, field)
|
||||
case "lastSession":
|
||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||
case "permission":
|
||||
@@ -10297,6 +10283,10 @@ func (ec *executionContext) fieldContext_ProfileEdge_node(_ context.Context, fie
|
||||
return ec.fieldContext_Profile_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Profile_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Profile_state(ctx, field)
|
||||
case "additionalEmailAddresses":
|
||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||
case "kind":
|
||||
@@ -12488,58 +12478,30 @@ func (ec *executionContext) fieldContext_SCIMEvent_errorMessage(_ context.Contex
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _SCIMEvent_profile(ctx context.Context, field graphql.CollectedField, obj *types.SCIMEvent) (ret graphql.Marshaler) {
|
||||
func (ec *executionContext) _SCIMEvent_userName(ctx context.Context, field graphql.CollectedField, obj *types.SCIMEvent) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_SCIMEvent_profile,
|
||||
ec.fieldContext_SCIMEvent_userName,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return ec.resolvers.SCIMEvent().Profile(ctx, obj)
|
||||
return obj.UserName, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalOProfile2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfile,
|
||||
ec.marshalNString2string,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_SCIMEvent_profile(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
func (ec *executionContext) fieldContext_SCIMEvent_userName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "SCIMEvent",
|
||||
Field: field,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
switch field.Name {
|
||||
case "id":
|
||||
return ec.fieldContext_Profile_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
||||
case "additionalEmailAddresses":
|
||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||
case "kind":
|
||||
return ec.fieldContext_Profile_kind(ctx, field)
|
||||
case "position":
|
||||
return ec.fieldContext_Profile_position(ctx, field)
|
||||
case "contractStartDate":
|
||||
return ec.fieldContext_Profile_contractStartDate(ctx, field)
|
||||
case "contractEndDate":
|
||||
return ec.fieldContext_Profile_contractEndDate(ctx, field)
|
||||
case "createdAt":
|
||||
return ec.fieldContext_Profile_createdAt(ctx, field)
|
||||
case "updatedAt":
|
||||
return ec.fieldContext_Profile_updatedAt(ctx, field)
|
||||
case "identity":
|
||||
return ec.fieldContext_Profile_identity(ctx, field)
|
||||
case "organization":
|
||||
return ec.fieldContext_Profile_organization(ctx, field)
|
||||
case "membership":
|
||||
return ec.fieldContext_Profile_membership(ctx, field)
|
||||
case "permission":
|
||||
return ec.fieldContext_Profile_permission(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Profile", field.Name)
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
@@ -12803,8 +12765,8 @@ func (ec *executionContext) fieldContext_SCIMEventEdge_node(_ context.Context, f
|
||||
return ec.fieldContext_SCIMEvent_responseBody(ctx, field)
|
||||
case "errorMessage":
|
||||
return ec.fieldContext_SCIMEvent_errorMessage(ctx, field)
|
||||
case "profile":
|
||||
return ec.fieldContext_SCIMEvent_profile(ctx, field)
|
||||
case "userName":
|
||||
return ec.fieldContext_SCIMEvent_userName(ctx, field)
|
||||
case "ipAddress":
|
||||
return ec.fieldContext_SCIMEvent_ipAddress(ctx, field)
|
||||
case "createdAt":
|
||||
@@ -13585,10 +13547,6 @@ func (ec *executionContext) fieldContext_UpdateMembershipPayload_membership(_ co
|
||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||
case "role":
|
||||
return ec.fieldContext_Membership_role(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Membership_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Membership_state(ctx, field)
|
||||
case "lastSession":
|
||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||
case "permission":
|
||||
@@ -13691,6 +13649,10 @@ func (ec *executionContext) fieldContext_UpdateProfilePayload_profile(_ context.
|
||||
return ec.fieldContext_Profile_id(ctx, field)
|
||||
case "fullName":
|
||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
||||
case "source":
|
||||
return ec.fieldContext_Profile_source(ctx, field)
|
||||
case "state":
|
||||
return ec.fieldContext_Profile_state(ctx, field)
|
||||
case "additionalEmailAddresses":
|
||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||
case "kind":
|
||||
@@ -18019,16 +17981,6 @@ func (ec *executionContext) _Membership(ctx context.Context, sel ast.SelectionSe
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "source":
|
||||
out.Values[i] = ec._Membership_source(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "state":
|
||||
out.Values[i] = ec._Membership_state(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "lastSession":
|
||||
field := field
|
||||
|
||||
@@ -19023,6 +18975,16 @@ func (ec *executionContext) _Profile(ctx context.Context, sel ast.SelectionSet,
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "source":
|
||||
out.Values[i] = ec._Profile_source(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "state":
|
||||
out.Values[i] = ec._Profile_state(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
case "additionalEmailAddresses":
|
||||
out.Values[i] = ec._Profile_additionalEmailAddresses(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@@ -20471,39 +20433,11 @@ func (ec *executionContext) _SCIMEvent(ctx context.Context, sel ast.SelectionSet
|
||||
out.Values[i] = ec._SCIMEvent_responseBody(ctx, field, obj)
|
||||
case "errorMessage":
|
||||
out.Values[i] = ec._SCIMEvent_errorMessage(ctx, field, obj)
|
||||
case "profile":
|
||||
field := field
|
||||
|
||||
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
}
|
||||
}()
|
||||
res = ec._SCIMEvent_profile(ctx, field, obj)
|
||||
return res
|
||||
case "userName":
|
||||
out.Values[i] = ec._SCIMEvent_userName(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
atomic.AddUint32(&out.Invalids, 1)
|
||||
}
|
||||
|
||||
if field.Deferrable != nil {
|
||||
dfs, ok := deferred[field.Deferrable.Label]
|
||||
di := 0
|
||||
if ok {
|
||||
dfs.AddField(field)
|
||||
di = len(dfs.Values) - 1
|
||||
} else {
|
||||
dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
|
||||
deferred[field.Deferrable.Label] = dfs
|
||||
}
|
||||
dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
|
||||
return innerFunc(ctx, dfs)
|
||||
})
|
||||
|
||||
// don't run the out.Concurrently() call below
|
||||
out.Values[i] = graphql.Null
|
||||
continue
|
||||
}
|
||||
|
||||
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
|
||||
case "ipAddress":
|
||||
out.Values[i] = ec._SCIMEvent_ipAddress(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@@ -22073,64 +22007,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource(ctx context.Context, v any) (coredata.MembershipSource, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource[tmp]
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipSource) graphql.Marshaler {
|
||||
_ = sel
|
||||
res := graphql.MarshalString(marshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource[v])
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource = map[string]coredata.MembershipSource{
|
||||
"MANUAL": coredata.MembershipSourceManual,
|
||||
"SAML": coredata.MembershipSourceSAML,
|
||||
"SCIM": coredata.MembershipSourceSCIM,
|
||||
}
|
||||
marshalNMembershipSource2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipSource = map[coredata.MembershipSource]string{
|
||||
coredata.MembershipSourceManual: "MANUAL",
|
||||
coredata.MembershipSourceSAML: "SAML",
|
||||
coredata.MembershipSourceSCIM: "SCIM",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState(ctx context.Context, v any) (coredata.MembershipState, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState[tmp]
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState(ctx context.Context, sel ast.SelectionSet, v coredata.MembershipState) graphql.Marshaler {
|
||||
_ = sel
|
||||
res := graphql.MarshalString(marshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState[v])
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState = map[string]coredata.MembershipState{
|
||||
"ACTIVE": coredata.MembershipStateActive,
|
||||
"INACTIVE": coredata.MembershipStateInactive,
|
||||
}
|
||||
marshalNMembershipState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMembershipState = map[coredata.MembershipState]string{
|
||||
coredata.MembershipStateActive: "ACTIVE",
|
||||
coredata.MembershipStateInactive: "INACTIVE",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx context.Context, v any) (page.OrderDirection, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection[tmp]
|
||||
@@ -22363,6 +22239,34 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState(ctx context.Context, v any) (coredata.ProfileState, error) {
|
||||
tmp, err := graphql.UnmarshalString(v)
|
||||
res := unmarshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState[tmp]
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState(ctx context.Context, sel ast.SelectionSet, v coredata.ProfileState) graphql.Marshaler {
|
||||
_ = sel
|
||||
res := graphql.MarshalString(marshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState[v])
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow")
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState = map[string]coredata.ProfileState{
|
||||
"ACTIVE": coredata.ProfileStateActive,
|
||||
"INACTIVE": coredata.ProfileStateInactive,
|
||||
}
|
||||
marshalNProfileState2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProfileState = map[coredata.ProfileState]string{
|
||||
coredata.ProfileStateActive: "ACTIVE",
|
||||
coredata.ProfileStateInactive: "INACTIVE",
|
||||
}
|
||||
)
|
||||
|
||||
func (ec *executionContext) unmarshalNReauthenticationReason2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐReauthenticationReason(ctx context.Context, v any) (types.ReauthenticationReason, error) {
|
||||
var res types.ReauthenticationReason
|
||||
err := res.UnmarshalGQL(v)
|
||||
|
||||
@@ -46,13 +46,13 @@ type (
|
||||
}
|
||||
|
||||
scimRequestContext struct {
|
||||
ctx context.Context
|
||||
config *coredata.SCIMConfiguration
|
||||
ipAddress net.IP
|
||||
method string
|
||||
path string
|
||||
membershipID *gid.GID
|
||||
handler *scimResourceHandler
|
||||
ctx context.Context
|
||||
config *coredata.SCIMConfiguration
|
||||
ipAddress net.IP
|
||||
method string
|
||||
path string
|
||||
userName string
|
||||
handler *scimResourceHandler
|
||||
}
|
||||
)
|
||||
|
||||
@@ -145,23 +145,23 @@ func (rc *scimRequestContext) logAndWrapError(err error, logMsg string) error {
|
||||
if errors.As(err, &scimErr) {
|
||||
errMsg := scimErr.Detail
|
||||
|
||||
// Don't reference membershipID for 404 errors - the resource doesn't exist
|
||||
membershipID := rc.membershipID
|
||||
// Don't reference profileID for 404 errors - the resource doesn't exist
|
||||
userName := rc.userName
|
||||
if scimErr.Status == http.StatusNotFound {
|
||||
membershipID = nil
|
||||
userName = ""
|
||||
}
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, membershipID, rc.ipAddress, scimErr.Status, &errMsg)
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, userName, rc.ipAddress, scimErr.Status, &errMsg)
|
||||
return err
|
||||
}
|
||||
|
||||
rc.handler.handler.logger.ErrorCtx(rc.ctx, logMsg, log.Error(err))
|
||||
errMsg := "internal server error"
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, rc.membershipID, rc.ipAddress, 500, &errMsg)
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, rc.userName, rc.ipAddress, 500, &errMsg)
|
||||
return scimerrors.ScimErrorInternal
|
||||
}
|
||||
|
||||
func (rc *scimRequestContext) logSuccess(statusCode int) {
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, rc.membershipID, rc.ipAddress, statusCode, nil)
|
||||
rc.handler.handler.iam.SCIMService.LogEvent(rc.ctx, rc.config, rc.method, rc.path, rc.userName, rc.ipAddress, statusCode, nil)
|
||||
}
|
||||
|
||||
func (h *scimResourceHandler) Create(r *http.Request, attributes scim.ResourceAttributes) (scim.Resource, error) {
|
||||
@@ -179,9 +179,10 @@ func (h *scimResourceHandler) Create(r *http.Request, attributes scim.ResourceAt
|
||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot create user")
|
||||
}
|
||||
|
||||
membershipID, _ := gid.ParseGID(resource.ID)
|
||||
rc.membershipID = &membershipID
|
||||
rc.userName = resource.Attributes["userName"].(string)
|
||||
|
||||
rc.logSuccess(201)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
@@ -195,18 +196,20 @@ func (h *scimResourceHandler) Get(r *http.Request, id string) (scim.Resource, er
|
||||
handler: h,
|
||||
}
|
||||
|
||||
membershipID, err := gid.ParseGID(id)
|
||||
profileID, err := gid.ParseGID(id)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid membership ID")
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid profile ID")
|
||||
}
|
||||
rc.membershipID = &membershipID
|
||||
|
||||
resource, err := h.handler.iam.SCIMService.GetUser(rc.ctx, rc.config, membershipID)
|
||||
resource, err := h.handler.iam.SCIMService.GetUser(rc.ctx, rc.config, profileID)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot get user")
|
||||
}
|
||||
|
||||
rc.userName = resource.Attributes["userName"].(string)
|
||||
|
||||
rc.logSuccess(200)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
@@ -256,18 +259,20 @@ func (h *scimResourceHandler) Replace(r *http.Request, id string, attributes sci
|
||||
handler: h,
|
||||
}
|
||||
|
||||
membershipID, err := gid.ParseGID(id)
|
||||
profileID, err := gid.ParseGID(id)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid membership ID")
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid profile ID")
|
||||
}
|
||||
rc.membershipID = &membershipID
|
||||
|
||||
resource, err := h.handler.iam.SCIMService.ReplaceUser(rc.ctx, rc.config, membershipID, attributes)
|
||||
resource, err := h.handler.iam.SCIMService.ReplaceUser(rc.ctx, rc.config, profileID, attributes)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot update user")
|
||||
}
|
||||
|
||||
rc.userName = resource.Attributes["userName"].(string)
|
||||
|
||||
rc.logSuccess(200)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
@@ -281,18 +286,20 @@ func (h *scimResourceHandler) Patch(r *http.Request, id string, operations []sci
|
||||
handler: h,
|
||||
}
|
||||
|
||||
membershipID, err := gid.ParseGID(id)
|
||||
profileID, err := gid.ParseGID(id)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid membership ID")
|
||||
return scim.Resource{}, rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid profile ID")
|
||||
}
|
||||
rc.membershipID = &membershipID
|
||||
|
||||
resource, err := h.handler.iam.SCIMService.PatchUser(rc.ctx, rc.config, membershipID, operations)
|
||||
resource, err := h.handler.iam.SCIMService.PatchUser(rc.ctx, rc.config, profileID, operations)
|
||||
if err != nil {
|
||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot patch user")
|
||||
}
|
||||
|
||||
rc.userName = resource.Attributes["userName"].(string)
|
||||
|
||||
rc.logSuccess(200)
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
@@ -306,19 +313,20 @@ func (h *scimResourceHandler) Delete(r *http.Request, id string) error {
|
||||
handler: h,
|
||||
}
|
||||
|
||||
membershipID, err := gid.ParseGID(id)
|
||||
profileID, err := gid.ParseGID(id)
|
||||
if err != nil {
|
||||
return rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid membership ID")
|
||||
return rc.logAndWrapError(scimerrors.ScimErrorResourceNotFound(id), "invalid profile ID")
|
||||
}
|
||||
rc.membershipID = &membershipID
|
||||
|
||||
err = h.handler.iam.SCIMService.DeleteUser(rc.ctx, rc.config, membershipID)
|
||||
err = h.handler.iam.SCIMService.DeleteUser(rc.ctx, rc.config, profileID)
|
||||
if err != nil {
|
||||
return rc.logAndWrapError(err, "cannot delete user")
|
||||
}
|
||||
|
||||
rc.membershipID = nil
|
||||
rc.userName = ""
|
||||
|
||||
rc.logSuccess(204)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,5 @@ func NewMembership(membership *coredata.Membership) *Membership {
|
||||
ID: membership.ID,
|
||||
CreatedAt: membership.CreatedAt,
|
||||
Role: membership.Role,
|
||||
Source: membership.Source,
|
||||
State: membership.State,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ func NewProfile(profile *coredata.MembershipProfile) *Profile {
|
||||
return &Profile{
|
||||
ID: profile.ID,
|
||||
FullName: profile.FullName,
|
||||
Source: profile.Source.String(),
|
||||
State: profile.State,
|
||||
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
|
||||
Kind: profile.Kind,
|
||||
Position: profile.Position,
|
||||
|
||||
@@ -64,6 +64,7 @@ func NewSCIMEvent(scimEvent *coredata.SCIMEvent) *SCIMEvent {
|
||||
ID: scimEvent.ID,
|
||||
Method: scimEvent.Method,
|
||||
Path: scimEvent.Path,
|
||||
UserName: scimEvent.UserName,
|
||||
StatusCode: scimEvent.StatusCode,
|
||||
RequestBody: scimEvent.RequestBody,
|
||||
ResponseBody: scimEvent.ResponseBody,
|
||||
@@ -72,12 +73,5 @@ func NewSCIMEvent(scimEvent *coredata.SCIMEvent) *SCIMEvent {
|
||||
CreatedAt: scimEvent.CreatedAt,
|
||||
}
|
||||
|
||||
// FIXME: profile
|
||||
// if scimEvent.MembershipID != nil {
|
||||
// event.Membership = &Membership{
|
||||
// ID: *scimEvent.MembershipID,
|
||||
// }
|
||||
// }
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
@@ -220,13 +220,11 @@ type InviteMemberPayload struct {
|
||||
}
|
||||
|
||||
type Membership struct {
|
||||
ID gid.GID `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
Source coredata.MembershipSource `json:"source"`
|
||||
State coredata.MembershipState `json:"state"`
|
||||
LastSession *Session `json:"lastSession,omitempty"`
|
||||
Permission bool `json:"permission"`
|
||||
ID gid.GID `json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
LastSession *Session `json:"lastSession,omitempty"`
|
||||
Permission bool `json:"permission"`
|
||||
}
|
||||
|
||||
func (Membership) IsNode() {}
|
||||
@@ -298,6 +296,8 @@ type PersonalAPIKeyEdge struct {
|
||||
type Profile struct {
|
||||
ID gid.GID `json:"id"`
|
||||
FullName string `json:"fullName"`
|
||||
Source string `json:"source"`
|
||||
State coredata.ProfileState `json:"state"`
|
||||
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
|
||||
Kind coredata.MembershipProfileKind `json:"kind"`
|
||||
Position *string `json:"position,omitempty"`
|
||||
@@ -452,7 +452,7 @@ type SCIMEvent struct {
|
||||
RequestBody *string `json:"requestBody,omitempty"`
|
||||
ResponseBody *string `json:"responseBody,omitempty"`
|
||||
ErrorMessage *string `json:"errorMessage,omitempty"`
|
||||
Profile *Profile `json:"profile,omitempty"`
|
||||
UserName string `json:"userName"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Permission bool `json:"permission"`
|
||||
|
||||
@@ -360,9 +360,9 @@ func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput)
|
||||
if err != nil {
|
||||
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
||||
var errMembershipNotFound *iam.ErrMembershipNotFound
|
||||
var errMembershipInactive *iam.ErrMembershipInactive
|
||||
var errUserInactive *iam.ErrUserInactive
|
||||
|
||||
if errors.As(err, &errMembershipNotFound) || errors.As(err, &errMembershipInactive) {
|
||||
if errors.As(err, &errMembershipNotFound) || errors.As(err, &errUserInactive) {
|
||||
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
||||
}
|
||||
|
||||
@@ -890,13 +890,13 @@ func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteM
|
||||
)
|
||||
if err != nil {
|
||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||
var errMembershipAlreadyExists *iam.ErrMembershipAlreadyExists
|
||||
var errUserAlreadyExists *iam.ErrUserAlreadyExists
|
||||
|
||||
if errors.As(err, &errOrganizationNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
if errors.As(err, &errMembershipAlreadyExists) {
|
||||
if errors.As(err, &errUserAlreadyExists) {
|
||||
return nil, gqlutils.Conflict(ctx, err)
|
||||
}
|
||||
|
||||
@@ -994,7 +994,7 @@ func (r *mutationResolver) RemoveMember(ctx context.Context, input types.RemoveM
|
||||
|
||||
err := r.iam.OrganizationService.RemoveMember(ctx, input.OrganizationID, input.MembershipID)
|
||||
if err != nil {
|
||||
var errManagedBySCIM *iam.ErrMembershipManagedBySCIM
|
||||
var errManagedBySCIM *iam.ErrUserManagedBySCIM
|
||||
var errLastActiveOwner *iam.ErrLastActiveOwner
|
||||
|
||||
if errors.As(err, &errManagedBySCIM) {
|
||||
@@ -1889,11 +1889,6 @@ func (r *sCIMConfigurationResolver) Permission(ctx context.Context, obj *types.S
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
}
|
||||
|
||||
// Profile is the resolver for the profile field.
|
||||
func (r *sCIMEventResolver) Profile(ctx context.Context, obj *types.SCIMEvent) (*types.Profile, error) {
|
||||
panic(fmt.Errorf("not implemented: Profile - profile"))
|
||||
}
|
||||
|
||||
// Permission is the resolver for the permission field.
|
||||
func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) {
|
||||
return r.Resolver.Permission(ctx, obj, action)
|
||||
|
||||
Reference in New Issue
Block a user