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
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -222,6 +222,20 @@ return {
|
|||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v12/*: any*/),
|
(v12/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "source",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "state",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -259,20 +273,6 @@ return {
|
|||||||
"name": "role",
|
"name": "role",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "source",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "state",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"alias": "canUpdate",
|
"alias": "canUpdate",
|
||||||
"args": [
|
"args": [
|
||||||
@@ -430,12 +430,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "25aa17e466fdec627ba3d24d5230301b",
|
"cacheID": "cc77d44877ae736c858e7458b525d75a",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "PeopleListFragment_RefetchQuery",
|
"name": "PeopleListFragment_RefetchQuery",
|
||||||
"operationKind": "query",
|
"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
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -10,9 +10,8 @@
|
|||||||
|
|
||||||
import { ReaderFragment } from 'relay-runtime';
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
|
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 ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
|
||||||
|
export type ProfileState = "ACTIVE" | "INACTIVE";
|
||||||
import { FragmentRefs } from "relay-runtime";
|
import { FragmentRefs } from "relay-runtime";
|
||||||
export type PeopleListItemFragment$data = {
|
export type PeopleListItemFragment$data = {
|
||||||
readonly canUpdate: boolean;
|
readonly canUpdate: boolean;
|
||||||
@@ -28,10 +27,10 @@ export type PeopleListItemFragment$data = {
|
|||||||
readonly canUpdate: boolean;
|
readonly canUpdate: boolean;
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly role: MembershipRole;
|
readonly role: MembershipRole;
|
||||||
readonly source: MembershipSource;
|
|
||||||
readonly state: MembershipState;
|
|
||||||
};
|
};
|
||||||
readonly position: string | null | undefined;
|
readonly position: string | null | undefined;
|
||||||
|
readonly source: string;
|
||||||
|
readonly state: ProfileState;
|
||||||
readonly " $fragmentType": "PeopleListItemFragment";
|
readonly " $fragmentType": "PeopleListItemFragment";
|
||||||
};
|
};
|
||||||
export type PeopleListItemFragment$key = {
|
export type PeopleListItemFragment$key = {
|
||||||
@@ -54,6 +53,20 @@ return {
|
|||||||
"name": "PeopleListItemFragment",
|
"name": "PeopleListItemFragment",
|
||||||
"selections": [
|
"selections": [
|
||||||
(v0/*: any*/),
|
(v0/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "source",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "state",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
@@ -93,20 +106,6 @@ return {
|
|||||||
"name": "role",
|
"name": "role",
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "source",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "state",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"alias": "canUpdate",
|
"alias": "canUpdate",
|
||||||
"args": [
|
"args": [
|
||||||
@@ -186,6 +185,6 @@ return {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "89702fbdf02294212269adb22efb65fe";
|
(node as any).hash = "ad9ed05edf606ec084c6f53462530b8b";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<e628337d33e88ca5cfe6e45a55eebe32>>
|
* @generated SignedSource<<7f025767ad277b8b8de6cbe9b1490312>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -345,6 +345,20 @@ return {
|
|||||||
"plural": false,
|
"plural": false,
|
||||||
"selections": [
|
"selections": [
|
||||||
(v10/*: any*/),
|
(v10/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "source",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "state",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
(v11/*: any*/),
|
(v11/*: any*/),
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -370,20 +384,6 @@ return {
|
|||||||
"selections": [
|
"selections": [
|
||||||
(v10/*: any*/),
|
(v10/*: any*/),
|
||||||
(v12/*: 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",
|
"alias": "canUpdate",
|
||||||
"args": [
|
"args": [
|
||||||
@@ -552,12 +552,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "4303ec6c2fa72181d747bb9053446aaa",
|
"cacheID": "b67de1f40d556a5e4a6d78104e2eb224",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "PeoplePageQuery",
|
"name": "PeoplePageQuery",
|
||||||
"operationKind": "query",
|
"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
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -16,10 +16,8 @@ export type SCIMEventListItemFragment$data = {
|
|||||||
readonly ipAddress: string;
|
readonly ipAddress: string;
|
||||||
readonly method: string;
|
readonly method: string;
|
||||||
readonly path: string;
|
readonly path: string;
|
||||||
readonly profile: {
|
|
||||||
readonly fullName: string;
|
|
||||||
} | null | undefined;
|
|
||||||
readonly statusCode: number;
|
readonly statusCode: number;
|
||||||
|
readonly userName: string;
|
||||||
readonly " $fragmentType": "SCIMEventListItemFragment";
|
readonly " $fragmentType": "SCIMEventListItemFragment";
|
||||||
};
|
};
|
||||||
export type SCIMEventListItemFragment$key = {
|
export type SCIMEventListItemFragment$key = {
|
||||||
@@ -78,19 +76,8 @@ const node: ReaderFragment = {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Profile",
|
"kind": "ScalarField",
|
||||||
"kind": "LinkedField",
|
"name": "userName",
|
||||||
"name": "profile",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "fullName",
|
|
||||||
"storageKey": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -98,6 +85,6 @@ const node: ReaderFragment = {
|
|||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
};
|
};
|
||||||
|
|
||||||
(node as any).hash = "b38af9ac9b660d3aab75e00e2c55dfb6";
|
(node as any).hash = "9df2e986643ab90589ebc6e4631cde24";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<22d63f81020d1e99f7cdb84fc23913a4>>
|
* @generated SignedSource<<08f3ffe97dd35388d5ce01977170773b>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -224,20 +224,8 @@ return {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Profile",
|
"kind": "ScalarField",
|
||||||
"kind": "LinkedField",
|
"name": "userName",
|
||||||
"name": "profile",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "fullName",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
(v8/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
(v7/*: any*/)
|
(v7/*: any*/)
|
||||||
@@ -315,12 +303,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "58b36de1b4c1f54de6433a95b297b570",
|
"cacheID": "5e80fcd5f3479d5b6bce65b59364d3f7",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "SCIMEventListPaginationQuery",
|
"name": "SCIMEventListPaginationQuery",
|
||||||
"operationKind": "query",
|
"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
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -277,20 +277,8 @@ return {
|
|||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
"args": null,
|
"args": null,
|
||||||
"concreteType": "Profile",
|
"kind": "ScalarField",
|
||||||
"kind": "LinkedField",
|
"name": "userName",
|
||||||
"name": "profile",
|
|
||||||
"plural": false,
|
|
||||||
"selections": [
|
|
||||||
{
|
|
||||||
"alias": null,
|
|
||||||
"args": null,
|
|
||||||
"kind": "ScalarField",
|
|
||||||
"name": "fullName",
|
|
||||||
"storageKey": null
|
|
||||||
},
|
|
||||||
(v3/*: any*/)
|
|
||||||
],
|
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
},
|
},
|
||||||
(v2/*: any*/)
|
(v2/*: any*/)
|
||||||
@@ -404,12 +392,12 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "3d49152bd3fd384a0b1de3d012268ebe",
|
"cacheID": "b98c546a2c8fc038c121c21f3b495a7d",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "SCIMSettingsPageQuery",
|
"name": "SCIMSettingsPageQuery",
|
||||||
"operationKind": "query",
|
"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`
|
const fragment = graphql`
|
||||||
fragment PeopleListItemFragment on Profile {
|
fragment PeopleListItemFragment on Profile {
|
||||||
id
|
id
|
||||||
|
source
|
||||||
|
state
|
||||||
fullName
|
fullName
|
||||||
kind
|
kind
|
||||||
position
|
position
|
||||||
membership @required(action: THROW) {
|
membership @required(action: THROW) {
|
||||||
id
|
id
|
||||||
role
|
role
|
||||||
source
|
|
||||||
state
|
|
||||||
canUpdate: permission(action: "iam:membership:update")
|
canUpdate: permission(action: "iam:membership:update")
|
||||||
canDelete: permission(action: "iam:membership-profile:delete")
|
canDelete: permission(action: "iam:membership-profile:delete")
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ export function MemberListItem(props: {
|
|||||||
|
|
||||||
const profile = useFragment<PeopleListItemFragment$key>(fragment, fKey);
|
const profile = useFragment<PeopleListItemFragment$key>(fragment, fKey);
|
||||||
|
|
||||||
const isInactive = profile.membership.state === "INACTIVE";
|
const isInactive = profile.state === "INACTIVE";
|
||||||
|
|
||||||
const [removeMembership, isRemoving] = useMutationWithToasts(
|
const [removeMembership, isRemoving] = useMutationWithToasts(
|
||||||
removeMemberMutation,
|
removeMemberMutation,
|
||||||
@@ -118,7 +118,7 @@ export function MemberListItem(props: {
|
|||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{profile.identity.email}
|
{profile.identity.email}
|
||||||
<Badge variant="info">{profile.membership.source}</Badge>
|
<Badge variant="info">{profile.source}</Badge>
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{profile.kind}</Td>
|
<Td>{profile.kind}</Td>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ const SCIMEventListItemFragment = graphql`
|
|||||||
errorMessage
|
errorMessage
|
||||||
ipAddress
|
ipAddress
|
||||||
createdAt
|
createdAt
|
||||||
profile {
|
userName
|
||||||
fullName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -89,8 +87,7 @@ export function SCIMEventListItem(props: {
|
|||||||
<div className="flex gap-8 text-sm">
|
<div className="flex gap-8 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-txt-secondary">User: </span>
|
<span className="text-txt-secondary">User: </span>
|
||||||
{/* FIXME */}
|
<span>{event.userName || "-"}</span>
|
||||||
{/* <span>{event.membership?.profile?.fullName || "-"}</span> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-txt-secondary">IP Address: </span>
|
<span className="text-txt-secondary">IP Address: </span>
|
||||||
|
|||||||
@@ -25,24 +25,17 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/mail"
|
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Membership struct {
|
Membership struct {
|
||||||
ID gid.GID `db:"id"`
|
ID gid.GID `db:"id"`
|
||||||
IdentityID gid.GID `db:"identity_id"`
|
IdentityID gid.GID `db:"identity_id"`
|
||||||
OrganizationID gid.GID `db:"organization_id"`
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
Role MembershipRole `db:"role"`
|
Role MembershipRole `db:"role"`
|
||||||
Source MembershipSource `db:"source"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
State MembershipState `db:"state"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
// 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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Memberships []*Membership
|
Memberships []*Membership
|
||||||
@@ -66,8 +59,6 @@ SELECT
|
|||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
role,
|
role,
|
||||||
source,
|
|
||||||
state,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -109,8 +100,6 @@ INSERT INTO
|
|||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
role,
|
role,
|
||||||
source,
|
|
||||||
state,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
@@ -120,8 +109,6 @@ VALUES (
|
|||||||
@identity_id,
|
@identity_id,
|
||||||
@organization_id,
|
@organization_id,
|
||||||
@role,
|
@role,
|
||||||
@source,
|
|
||||||
@state,
|
|
||||||
@created_at,
|
@created_at,
|
||||||
@updated_at
|
@updated_at
|
||||||
);
|
);
|
||||||
@@ -133,8 +120,6 @@ VALUES (
|
|||||||
"identity_id": m.IdentityID,
|
"identity_id": m.IdentityID,
|
||||||
"organization_id": m.OrganizationID,
|
"organization_id": m.OrganizationID,
|
||||||
"role": m.Role,
|
"role": m.Role,
|
||||||
"source": m.Source,
|
|
||||||
"state": m.State,
|
|
||||||
"created_at": m.CreatedAt,
|
"created_at": m.CreatedAt,
|
||||||
"updated_at": m.UpdatedAt,
|
"updated_at": m.UpdatedAt,
|
||||||
}
|
}
|
||||||
@@ -168,8 +153,6 @@ SELECT
|
|||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
role,
|
role,
|
||||||
source,
|
|
||||||
state,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -209,8 +192,7 @@ func (m *Membership) AuthorizationAttributes(ctx context.Context, conn pg.Conn)
|
|||||||
SELECT
|
SELECT
|
||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
role,
|
role
|
||||||
source
|
|
||||||
FROM
|
FROM
|
||||||
iam_memberships
|
iam_memberships
|
||||||
WHERE
|
WHERE
|
||||||
@@ -221,12 +203,10 @@ LIMIT 1;
|
|||||||
var identityID gid.GID
|
var identityID gid.GID
|
||||||
var organizationID gid.GID
|
var organizationID gid.GID
|
||||||
var role MembershipRole
|
var role MembershipRole
|
||||||
var source MembershipSource
|
|
||||||
if err := conn.QueryRow(ctx, q, m.ID).Scan(
|
if err := conn.QueryRow(ctx, q, m.ID).Scan(
|
||||||
&identityID,
|
&identityID,
|
||||||
&organizationID,
|
&organizationID,
|
||||||
&role,
|
&role,
|
||||||
&source,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, ErrResourceNotFound
|
return nil, ErrResourceNotFound
|
||||||
@@ -238,7 +218,6 @@ LIMIT 1;
|
|||||||
"identity_id": identityID.String(),
|
"identity_id": identityID.String(),
|
||||||
"organization_id": organizationID.String(),
|
"organization_id": organizationID.String(),
|
||||||
"role": role.String(),
|
"role": role.String(),
|
||||||
"source": source.String(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,8 +234,6 @@ SELECT
|
|||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
role,
|
role,
|
||||||
source,
|
|
||||||
state,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -299,8 +276,6 @@ UPDATE
|
|||||||
iam_memberships
|
iam_memberships
|
||||||
SET
|
SET
|
||||||
role = @role,
|
role = @role,
|
||||||
source = @source,
|
|
||||||
state = @state,
|
|
||||||
updated_at = @updated_at
|
updated_at = @updated_at
|
||||||
WHERE
|
WHERE
|
||||||
id = @id
|
id = @id
|
||||||
@@ -312,8 +287,6 @@ WHERE
|
|||||||
args := pgx.StrictNamedArgs{
|
args := pgx.StrictNamedArgs{
|
||||||
"id": m.ID,
|
"id": m.ID,
|
||||||
"role": m.Role,
|
"role": m.Role,
|
||||||
"source": m.Source,
|
|
||||||
"state": m.State,
|
|
||||||
"updated_at": m.UpdatedAt,
|
"updated_at": m.UpdatedAt,
|
||||||
}
|
}
|
||||||
maps.Copy(args, scope.SQLArguments())
|
maps.Copy(args, scope.SQLArguments())
|
||||||
@@ -358,180 +331,51 @@ WHERE
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Memberships) LoadByOrganizationID(
|
func (m *Membership) LoadActiveByIdentityIDAndOrganizationID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
scope Scoper,
|
identityID gid.GID,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
cursor *page.Cursor[MembershipOrderField],
|
|
||||||
filter *MembershipFilter,
|
|
||||||
) error {
|
) error {
|
||||||
query := `
|
q := `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
m.id,
|
||||||
identity_id,
|
m.identity_id,
|
||||||
organization_id,
|
m.organization_id,
|
||||||
role,
|
m.role,
|
||||||
source,
|
m.created_at,
|
||||||
state,
|
m.updated_at
|
||||||
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(*)
|
|
||||||
FROM
|
FROM
|
||||||
iam_memberships m
|
iam_memberships m
|
||||||
JOIN
|
INNER JOIN iam_membership_profiles p
|
||||||
identities i ON m.identity_id = i.id
|
ON p.identity_id = m.identity_id AND p.organization_id = m.organization_id
|
||||||
WHERE
|
WHERE
|
||||||
m.organization_id = @organization_id
|
p.state = @state
|
||||||
AND m.%s
|
AND m.identity_id = @identity_id
|
||||||
AND %s
|
AND m.organization_id = @organization_id
|
||||||
|
LIMIT 1
|
||||||
`
|
`
|
||||||
query = fmt.Sprintf(query, scope.SQLFragment(), filter.SQLFragment())
|
|
||||||
args := pgx.StrictNamedArgs{
|
args := pgx.StrictNamedArgs{
|
||||||
|
"state": ProfileStateActive,
|
||||||
|
"identity_id": identityID,
|
||||||
"organization_id": organizationID,
|
"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(
|
rows, err := conn.Query(ctx, q, args)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot query memberships: %w", err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot collect memberships: %w", err)
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
}
|
return ErrResourceNotFound
|
||||||
|
}
|
||||||
*m = memberships
|
|
||||||
return nil
|
return fmt.Errorf("cannot collect membership: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*m = *membership
|
||||||
return nil
|
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"`
|
IdentityID gid.GID `db:"identity_id"`
|
||||||
OrganizationID gid.GID `db:"organization_id"`
|
OrganizationID gid.GID `db:"organization_id"`
|
||||||
EmailAddress mail.Addr `db:"email_address"`
|
EmailAddress mail.Addr `db:"email_address"`
|
||||||
|
Source ProfileSource `db:"source"`
|
||||||
|
State ProfileState `db:"state"`
|
||||||
FullName string `db:"full_name"`
|
FullName string `db:"full_name"`
|
||||||
Kind MembershipProfileKind `db:"kind"`
|
Kind MembershipProfileKind `db:"kind"`
|
||||||
AdditionalEmailAddresses mail.Addrs `db:"additional_email_addresses"`
|
AdditionalEmailAddresses mail.Addrs `db:"additional_email_addresses"`
|
||||||
@@ -93,6 +95,8 @@ SELECT
|
|||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
i.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -149,6 +153,8 @@ SELECT
|
|||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
i.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -208,6 +214,8 @@ SELECT
|
|||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
i.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -257,30 +265,35 @@ func (p *MembershipProfiles) LoadByOrganizationID(
|
|||||||
q := `
|
q := `
|
||||||
WITH profiles AS (
|
WITH profiles AS (
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
p.id,
|
||||||
identity_id,
|
p.identity_id,
|
||||||
organization_id,
|
p.organization_id,
|
||||||
full_name,
|
i.email_address,
|
||||||
kind,
|
p.source,
|
||||||
additional_email_addresses,
|
p.state,
|
||||||
position,
|
p.full_name,
|
||||||
contract_start_date,
|
p.kind,
|
||||||
contract_end_date,
|
p.additional_email_addresses,
|
||||||
created_at,
|
p.position,
|
||||||
updated_at
|
p.contract_start_date,
|
||||||
|
p.contract_end_date,
|
||||||
|
p.created_at,
|
||||||
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
iam_membership_profiles
|
iam_membership_profiles p
|
||||||
|
INNER JOIN identities i ON i.id = p.identity_id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
p.%s
|
||||||
AND organization_id = @organization_id
|
AND p.organization_id = @organization_id
|
||||||
AND %s
|
|
||||||
AND %s
|
AND %s
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
p.id,
|
p.id,
|
||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
p.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -291,7 +304,8 @@ SELECT
|
|||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM profiles p
|
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())
|
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(
|
func (p *MembershipProfiles) LoadByIdentityID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
scope Scoper,
|
|
||||||
identityID gid.GID,
|
identityID gid.GID,
|
||||||
cursor *page.Cursor[MembershipProfileOrderField],
|
cursor *page.Cursor[MembershipProfileOrderField],
|
||||||
filter *MembershipProfileFilter,
|
filter *MembershipProfileFilter,
|
||||||
@@ -325,29 +338,34 @@ func (p *MembershipProfiles) LoadByIdentityID(
|
|||||||
q := `
|
q := `
|
||||||
WITH profiles AS (
|
WITH profiles AS (
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
p.id,
|
||||||
identity_id,
|
p.identity_id,
|
||||||
organization_id,
|
p.organization_id,
|
||||||
full_name,
|
i.email_address,
|
||||||
kind,
|
p.source,
|
||||||
additional_email_addresses,
|
p.state,
|
||||||
position,
|
p.full_name,
|
||||||
contract_start_date,
|
p.kind,
|
||||||
contract_end_date,
|
p.additional_email_addresses,
|
||||||
created_at,
|
p.position,
|
||||||
updated_at
|
p.contract_start_date,
|
||||||
|
p.contract_end_date,
|
||||||
|
p.created_at,
|
||||||
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
iam_membership_profiles
|
iam_membership_profiles p
|
||||||
|
INNER JOIN identities i ON i.id = p.identity_id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
p.identity_id = @identity_id
|
||||||
AND identity_id = @identity_id
|
|
||||||
AND %s
|
AND %s
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
p.id,
|
p.id,
|
||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
p.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -358,16 +376,14 @@ SELECT
|
|||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM profiles p
|
FROM profiles p
|
||||||
INNER JOIN identities i ON i.id = p.identity_id
|
|
||||||
INNER JOIN organizations o ON o.id = p.organization_id
|
INNER JOIN organizations o ON o.id = p.organization_id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%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}
|
args := pgx.NamedArgs{"identity_id": identityID}
|
||||||
maps.Copy(args, scope.SQLArguments())
|
|
||||||
|
|
||||||
rows, err := conn.Query(ctx, q, args)
|
rows, err := conn.Query(ctx, q, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -600,6 +616,8 @@ WITH attendees AS (
|
|||||||
p.identity_id,
|
p.identity_id,
|
||||||
p.organization_id,
|
p.organization_id,
|
||||||
i.email_address,
|
i.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
p.kind,
|
p.kind,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
@@ -624,6 +642,8 @@ SELECT
|
|||||||
organization_id,
|
organization_id,
|
||||||
kind,
|
kind,
|
||||||
email_address,
|
email_address,
|
||||||
|
source,
|
||||||
|
state,
|
||||||
full_name,
|
full_name,
|
||||||
additional_email_addresses,
|
additional_email_addresses,
|
||||||
position,
|
position,
|
||||||
@@ -684,6 +704,8 @@ SELECT
|
|||||||
p.kind,
|
p.kind,
|
||||||
p.full_name,
|
p.full_name,
|
||||||
i.email_address,
|
i.email_address,
|
||||||
|
p.source,
|
||||||
|
p.state,
|
||||||
p.additional_email_addresses,
|
p.additional_email_addresses,
|
||||||
p.position,
|
p.position,
|
||||||
p.contract_start_date,
|
p.contract_start_date,
|
||||||
@@ -725,10 +747,11 @@ func (p *MembershipProfiles) CountByIdentityID(
|
|||||||
SELECT
|
SELECT
|
||||||
COUNT(*)
|
COUNT(*)
|
||||||
FROM
|
FROM
|
||||||
iam_membership_profiles
|
iam_membership_profiles p
|
||||||
|
INNER JOIN identities i ON i.id = p.identity_id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND identity_id = @identity_id
|
AND p.identity_id = @identity_id
|
||||||
`
|
`
|
||||||
|
|
||||||
q = fmt.Sprintf(q, filter.SQLFragment())
|
q = fmt.Sprintf(q, filter.SQLFragment())
|
||||||
@@ -758,11 +781,12 @@ func (p *MembershipProfiles) CountByOrganizationID(
|
|||||||
SELECT
|
SELECT
|
||||||
COUNT(*)
|
COUNT(*)
|
||||||
FROM
|
FROM
|
||||||
iam_membership_profiles
|
iam_membership_profiles p
|
||||||
|
INNER JOIN identities i ON i.id = p.identity_id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
p.%s
|
||||||
AND %s
|
AND %s
|
||||||
AND organization_id = @organization_id
|
AND p.organization_id = @organization_id
|
||||||
`
|
`
|
||||||
|
|
||||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||||
@@ -782,6 +806,45 @@ WHERE
|
|||||||
return count, nil
|
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(
|
func (p *MembershipProfile) Insert(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
@@ -793,6 +856,8 @@ INSERT INTO
|
|||||||
id,
|
id,
|
||||||
identity_id,
|
identity_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
|
source,
|
||||||
|
state,
|
||||||
full_name,
|
full_name,
|
||||||
kind,
|
kind,
|
||||||
additional_email_addresses,
|
additional_email_addresses,
|
||||||
@@ -807,6 +872,8 @@ VALUES (
|
|||||||
@id,
|
@id,
|
||||||
@identity_id,
|
@identity_id,
|
||||||
@organization_id,
|
@organization_id,
|
||||||
|
@source,
|
||||||
|
@state,
|
||||||
@full_name,
|
@full_name,
|
||||||
@kind,
|
@kind,
|
||||||
COALESCE(@additional_email_addresses, '{}'::CITEXT[]),
|
COALESCE(@additional_email_addresses, '{}'::CITEXT[]),
|
||||||
@@ -823,6 +890,8 @@ VALUES (
|
|||||||
"id": p.ID,
|
"id": p.ID,
|
||||||
"identity_id": p.IdentityID,
|
"identity_id": p.IdentityID,
|
||||||
"organization_id": p.OrganizationID,
|
"organization_id": p.OrganizationID,
|
||||||
|
"source": p.Source,
|
||||||
|
"state": p.State,
|
||||||
"full_name": p.FullName,
|
"full_name": p.FullName,
|
||||||
"kind": p.Kind,
|
"kind": p.Kind,
|
||||||
"additional_email_addresses": p.AdditionalEmailAddresses,
|
"additional_email_addresses": p.AdditionalEmailAddresses,
|
||||||
@@ -850,6 +919,8 @@ func (p *MembershipProfile) Update(
|
|||||||
UPDATE
|
UPDATE
|
||||||
iam_membership_profiles
|
iam_membership_profiles
|
||||||
SET
|
SET
|
||||||
|
source = @source,
|
||||||
|
state = @state,
|
||||||
full_name = @full_name,
|
full_name = @full_name,
|
||||||
kind = @kind,
|
kind = @kind,
|
||||||
additional_email_addresses = @additional_email_addresses,
|
additional_email_addresses = @additional_email_addresses,
|
||||||
@@ -866,6 +937,8 @@ WHERE
|
|||||||
|
|
||||||
args := pgx.StrictNamedArgs{
|
args := pgx.StrictNamedArgs{
|
||||||
"id": p.ID,
|
"id": p.ID,
|
||||||
|
"source": p.Source,
|
||||||
|
"state": p.State,
|
||||||
"full_name": p.FullName,
|
"full_name": p.FullName,
|
||||||
"kind": p.Kind,
|
"kind": p.Kind,
|
||||||
"additional_email_addresses": p.AdditionalEmailAddresses,
|
"additional_email_addresses": p.AdditionalEmailAddresses,
|
||||||
@@ -888,6 +961,38 @@ WHERE
|
|||||||
return nil
|
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(
|
func (p *MembershipProfile) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Conn,
|
conn pg.Conn,
|
||||||
|
|||||||
@@ -18,12 +18,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.probo.inc/probo/pkg/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
MembershipProfileFilter struct {
|
MembershipProfileFilter struct {
|
||||||
excludeContractEnded *bool
|
excludeContractEnded *bool
|
||||||
currentDate time.Time
|
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 {
|
func (f *MembershipProfileFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||||
return pgx.StrictNamedArgs{
|
return pgx.StrictNamedArgs{
|
||||||
|
"filter_email": f.email,
|
||||||
"exclude_contract_ended": f.excludeContractEnded,
|
"exclude_contract_ended": f.excludeContractEnded,
|
||||||
"current_date": f.currentDate,
|
"current_date": f.currentDate,
|
||||||
|
"filter_state": f.state,
|
||||||
|
"filter_source": f.source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *MembershipProfileFilter) SQLFragment() string {
|
func (f *MembershipProfileFilter) SQLFragment() string {
|
||||||
return `
|
return `
|
||||||
(
|
(
|
||||||
|
CASE
|
||||||
|
WHEN @filter_email::text IS NOT NULL THEN
|
||||||
|
i.email_address = @filter_email::text
|
||||||
|
ELSE TRUE
|
||||||
|
END
|
||||||
|
)
|
||||||
|
AND (
|
||||||
CASE
|
CASE
|
||||||
WHEN @exclude_contract_ended::boolean IS NOT NULL AND @exclude_contract_ended::boolean = true THEN
|
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
|
ELSE TRUE
|
||||||
END
|
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
|
ALTER TABLE
|
||||||
iam_membership_profiles DROP COLUMN membership_id;
|
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"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MembershipSource string
|
type ProfileSource string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MembershipSourceManual MembershipSource = "MANUAL"
|
ProfileSourceManual ProfileSource = "MANUAL"
|
||||||
MembershipSourceSAML MembershipSource = "SAML"
|
ProfileSourceSAML ProfileSource = "SAML"
|
||||||
MembershipSourceSCIM MembershipSource = "SCIM"
|
ProfileSourceSCIM ProfileSource = "SCIM"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s MembershipSource) String() string {
|
func (s ProfileSource) String() string {
|
||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MembershipSource) Scan(value any) error {
|
func (s *ProfileSource) Scan(value any) error {
|
||||||
var str string
|
var str string
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -39,22 +39,22 @@ func (s *MembershipSource) Scan(value any) error {
|
|||||||
case []byte:
|
case []byte:
|
||||||
str = string(v)
|
str = string(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported type for MembershipSource: %T", value)
|
return fmt.Errorf("unsupported type for ProfileSource: %T", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch str {
|
switch str {
|
||||||
case "MANUAL":
|
case "MANUAL":
|
||||||
*s = MembershipSourceManual
|
*s = ProfileSourceManual
|
||||||
case "SAML":
|
case "SAML":
|
||||||
*s = MembershipSourceSAML
|
*s = ProfileSourceSAML
|
||||||
case "SCIM":
|
case "SCIM":
|
||||||
*s = MembershipSourceSCIM
|
*s = ProfileSourceSCIM
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid MembershipSource value: %q", str)
|
return fmt.Errorf("invalid ProfileSource value: %q", str)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s MembershipSource) Value() (driver.Value, error) {
|
func (s ProfileSource) Value() (driver.Value, error) {
|
||||||
return s.String(), nil
|
return s.String(), nil
|
||||||
}
|
}
|
||||||
@@ -19,18 +19,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MembershipState string
|
type ProfileState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MembershipStateActive MembershipState = "ACTIVE"
|
ProfileStateActive ProfileState = "ACTIVE"
|
||||||
MembershipStateInactive MembershipState = "INACTIVE"
|
ProfileStateInactive ProfileState = "INACTIVE"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s MembershipState) String() string {
|
func (s ProfileState) String() string {
|
||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MembershipState) Scan(value any) error {
|
func (s *ProfileState) Scan(value any) error {
|
||||||
var str string
|
var str string
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -38,20 +38,20 @@ func (s *MembershipState) Scan(value any) error {
|
|||||||
case []byte:
|
case []byte:
|
||||||
str = string(v)
|
str = string(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported type for MembershipState: %T", value)
|
return fmt.Errorf("unsupported type for ProfileState: %T", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch str {
|
switch str {
|
||||||
case "ACTIVE":
|
case "ACTIVE":
|
||||||
*s = MembershipStateActive
|
*s = ProfileStateActive
|
||||||
case "INACTIVE":
|
case "INACTIVE":
|
||||||
*s = MembershipStateInactive
|
*s = ProfileStateInactive
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid MembershipState value: %q", str)
|
return fmt.Errorf("invalid ProfileState value: %q", str)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s MembershipState) Value() (driver.Value, error) {
|
func (s ProfileState) Value() (driver.Value, error) {
|
||||||
return s.String(), nil
|
return s.String(), nil
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ type (
|
|||||||
ResponseBody *string `db:"response_body"`
|
ResponseBody *string `db:"response_body"`
|
||||||
StatusCode int `db:"status_code"`
|
StatusCode int `db:"status_code"`
|
||||||
ErrorMessage *string `db:"error_message"`
|
ErrorMessage *string `db:"error_message"`
|
||||||
MembershipID *gid.GID `db:"membership_id"`
|
UserName string `db:"user_name"`
|
||||||
IPAddress net.IP `db:"ip_address"`
|
IPAddress net.IP `db:"ip_address"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ SELECT
|
|||||||
response_body,
|
response_body,
|
||||||
status_code,
|
status_code,
|
||||||
error_message,
|
error_message,
|
||||||
membership_id,
|
user_name,
|
||||||
ip_address,
|
ip_address,
|
||||||
created_at
|
created_at
|
||||||
FROM
|
FROM
|
||||||
@@ -139,7 +139,7 @@ INSERT INTO iam_scim_events (
|
|||||||
response_body,
|
response_body,
|
||||||
status_code,
|
status_code,
|
||||||
error_message,
|
error_message,
|
||||||
membership_id,
|
user_name,
|
||||||
ip_address,
|
ip_address,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@@ -153,7 +153,7 @@ INSERT INTO iam_scim_events (
|
|||||||
@response_body,
|
@response_body,
|
||||||
@status_code,
|
@status_code,
|
||||||
@error_message,
|
@error_message,
|
||||||
@membership_id,
|
@user_name,
|
||||||
@ip_address,
|
@ip_address,
|
||||||
@created_at
|
@created_at
|
||||||
)
|
)
|
||||||
@@ -170,7 +170,7 @@ INSERT INTO iam_scim_events (
|
|||||||
"response_body": s.ResponseBody,
|
"response_body": s.ResponseBody,
|
||||||
"status_code": s.StatusCode,
|
"status_code": s.StatusCode,
|
||||||
"error_message": s.ErrorMessage,
|
"error_message": s.ErrorMessage,
|
||||||
"membership_id": s.MembershipID,
|
"user_name": s.UserName,
|
||||||
"ip_address": s.IPAddress,
|
"ip_address": s.IPAddress,
|
||||||
"created_at": s.CreatedAt,
|
"created_at": s.CreatedAt,
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ SELECT
|
|||||||
response_body,
|
response_body,
|
||||||
status_code,
|
status_code,
|
||||||
error_message,
|
error_message,
|
||||||
membership_id,
|
user_name,
|
||||||
ip_address,
|
ip_address,
|
||||||
created_at
|
created_at
|
||||||
FROM
|
FROM
|
||||||
@@ -281,7 +281,7 @@ SELECT
|
|||||||
response_body,
|
response_body,
|
||||||
status_code,
|
status_code,
|
||||||
error_message,
|
error_message,
|
||||||
membership_id,
|
user_name,
|
||||||
ip_address,
|
ip_address,
|
||||||
created_at
|
created_at
|
||||||
FROM
|
FROM
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ func (s *AccountService) AcceptInvitation(
|
|||||||
) (*coredata.Invitation, *coredata.Membership, error) {
|
) (*coredata.Invitation, *coredata.Membership, error) {
|
||||||
var (
|
var (
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
|
profile = &coredata.MembershipProfile{}
|
||||||
membership = &coredata.Membership{}
|
membership = &coredata.Membership{}
|
||||||
invitation = &coredata.Invitation{}
|
invitation = &coredata.Invitation{}
|
||||||
)
|
)
|
||||||
@@ -228,47 +229,24 @@ func (s *AccountService) AcceptInvitation(
|
|||||||
tenantID := invitation.OrganizationID.TenantID()
|
tenantID := invitation.OrganizationID.TenantID()
|
||||||
scope := coredata.NewScope(invitation.OrganizationID.TenantID())
|
scope := coredata.NewScope(invitation.OrganizationID.TenantID())
|
||||||
|
|
||||||
existingMembership := &coredata.Membership{}
|
existingProfile := &coredata.MembershipProfile{}
|
||||||
if err := existingMembership.LoadByIdentityAndOrg(
|
if err := existingProfile.LoadByIdentityIDAndOrganizationID(
|
||||||
ctx,
|
ctx,
|
||||||
tx,
|
tx,
|
||||||
scope,
|
scope,
|
||||||
identityID,
|
identityID,
|
||||||
invitation.OrganizationID,
|
invitation.OrganizationID,
|
||||||
); err != nil && err != coredata.ErrResourceNotFound {
|
); err != nil {
|
||||||
return fmt.Errorf("cannot load existing membership: %w", err)
|
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
}
|
return fmt.Errorf("cannot load existing profile: %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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
membership = existingMembership
|
profile = &coredata.MembershipProfile{
|
||||||
} 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{
|
|
||||||
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
ID: gid.New(tenantID, coredata.MembershipProfileEntityType),
|
||||||
IdentityID: identity.ID,
|
IdentityID: identity.ID,
|
||||||
OrganizationID: invitation.OrganizationID,
|
OrganizationID: invitation.OrganizationID,
|
||||||
|
Source: coredata.ProfileSourceManual,
|
||||||
|
State: coredata.ProfileStateActive,
|
||||||
FullName: identity.FullName,
|
FullName: identity.FullName,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -277,6 +255,51 @@ func (s *AccountService) AcceptInvitation(
|
|||||||
if err := profile.Insert(ctx, tx); err != nil {
|
if err := profile.Insert(ctx, tx); err != nil {
|
||||||
return fmt.Errorf("cannot insert profile: %w", err)
|
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
|
invitation.AcceptedAt = &now
|
||||||
@@ -384,32 +407,6 @@ func (s *AccountService) CountPendingInvitations(
|
|||||||
return count, nil
|
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 {
|
func (s AccountService) ChangePassword(ctx context.Context, identityID gid.GID, req *ChangePasswordRequest) error {
|
||||||
if err := req.Validate(); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
return fmt.Errorf("invalid request: %w", err)
|
return fmt.Errorf("invalid request: %w", err)
|
||||||
@@ -857,7 +854,7 @@ func (s *AccountService) ListProfilesForIdentity(
|
|||||||
err := s.pg.WithConn(
|
err := s.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
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)
|
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"]
|
resourceOrgID := resourceAttrs["organization_id"]
|
||||||
|
|
||||||
// Find role for resource's organization
|
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot load memberships for principal: %w", err)
|
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
|
// Check whether the viewer is currently assuming the org of the accessed resource
|
||||||
if membership != nil && params.Session != nil {
|
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)
|
return NewInsufficientPermissionsError(params.Principal, params.Resource, params.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authorizer) loadMemberships(ctx context.Context, conn pg.Conn, principalID gid.GID) (coredata.Memberships, error) {
|
func (a *Authorizer) loadMembership(
|
||||||
var memberships coredata.Memberships
|
ctx context.Context,
|
||||||
if err := memberships.LoadAllByIdentityID(ctx, conn, principalID); err != nil {
|
conn pg.Conn,
|
||||||
return nil, fmt.Errorf("cannot load memberships: %w", err)
|
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(
|
func (a *Authorizer) getActiveChildSessionForMembership(
|
||||||
@@ -240,13 +258,3 @@ func (a *Authorizer) buildPoliciesForRole(role string) []*policy.Policy {
|
|||||||
|
|
||||||
return policies
|
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)
|
return fmt.Sprintf("membership %q not found", e.MembershipID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrMembershipInactive struct {
|
type ErrUserInactive struct {
|
||||||
MembershipID gid.GID
|
ProfileID gid.GID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMembershipInactiveError(membershipID gid.GID) error {
|
func NewUserInactiveError(profileID gid.GID) error {
|
||||||
return &ErrMembershipInactive{MembershipID: membershipID}
|
return &ErrUserInactive{ProfileID: profileID}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMembershipInactive) Error() string {
|
func (e ErrUserInactive) Error() string {
|
||||||
return fmt.Sprintf("membership %q is inactive", e.MembershipID)
|
return fmt.Sprintf("user %q is inactive", e.ProfileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrMembershipManagedBySCIM struct {
|
type ErrUserManagedBySCIM struct {
|
||||||
MembershipID gid.GID
|
ProfileID gid.GID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMembershipManagedBySCIMError(membershipID gid.GID) error {
|
func NewUserManagedBySCIMError(profileID gid.GID) error {
|
||||||
return &ErrMembershipManagedBySCIM{MembershipID: membershipID}
|
return &ErrUserManagedBySCIM{ProfileID: profileID}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMembershipManagedBySCIM) Error() string {
|
func (e ErrUserManagedBySCIM) Error() string {
|
||||||
return fmt.Sprintf("membership %q is managed by SCIM and cannot be deleted manually", e.MembershipID)
|
return fmt.Sprintf("user %q is managed by SCIM and cannot be deleted manually", e.ProfileID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrLastActiveOwner struct {
|
type ErrLastActiveOwner struct {
|
||||||
@@ -156,7 +156,7 @@ func NewLastActiveOwnerError(membershipID gid.GID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrLastActiveOwner) Error() string {
|
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 }
|
type ErrOrganizationNotFound struct{ OrganizationID gid.GID }
|
||||||
@@ -220,17 +220,17 @@ func (e ErrSessionExpired) Error() string {
|
|||||||
return fmt.Sprintf("session %q expired", e.SessionID)
|
return fmt.Sprintf("session %q expired", e.SessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrMembershipAlreadyExists struct {
|
type ErrUserAlreadyExists struct {
|
||||||
IdentityID gid.GID
|
IdentityID gid.GID
|
||||||
OrganizationID gid.GID
|
OrganizationID gid.GID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMembershipAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
func NewUserAlreadyExistsError(identityID gid.GID, organizationID gid.GID) error {
|
||||||
return &ErrMembershipAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
return &ErrUserAlreadyExists{IdentityID: identityID, OrganizationID: organizationID}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMembershipAlreadyExists) Error() string {
|
func (e ErrUserAlreadyExists) Error() string {
|
||||||
return fmt.Sprintf("membership already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
return fmt.Sprintf("user already exists for identity %q in organization %q", e.IdentityID, e.OrganizationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
|
type ErrSAMLConfigurationNotFound struct{ ConfigID gid.GID }
|
||||||
|
|||||||
@@ -212,33 +212,6 @@ func NewOrganizationService(svc *Service) *OrganizationService {
|
|||||||
return &OrganizationService{Service: svc}
|
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(
|
func (s *OrganizationService) UpdateMempership(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
@@ -282,48 +255,53 @@ func (s *OrganizationService) UpdateMempership(
|
|||||||
func (s *OrganizationService) RemoveMember(
|
func (s *OrganizationService) RemoveMember(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
membershipID gid.GID,
|
profileID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
scope := coredata.NewScopeFromObjectID(organizationID)
|
scope := coredata.NewScopeFromObjectID(organizationID)
|
||||||
|
|
||||||
return s.pg.WithTx(
|
return s.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
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 {
|
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)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.OrganizationID != organizationID {
|
if membership.Role == coredata.MembershipRoleOwner && profile.State == coredata.ProfileStateActive {
|
||||||
return NewMembershipNotFoundError(membership.ID)
|
profiles := coredata.MembershipProfiles{}
|
||||||
}
|
count, err := profiles.CountActiveOwnerByOrganizationID(ctx, tx, scope, organizationID)
|
||||||
|
|
||||||
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 err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot count active owners: %w", err)
|
return fmt.Errorf("cannot count active owners: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if count <= 1 {
|
if count <= 1 {
|
||||||
return NewLastActiveOwnerError(membershipID)
|
return NewLastActiveOwnerError(profileID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := membership.Delete(ctx, tx, scope, membershipID)
|
if err := profile.Delete(ctx, tx, scope, profileID); err != nil {
|
||||||
if 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)
|
return fmt.Errorf("cannot delete membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,14 +444,14 @@ func (s *OrganizationService) InviteMember(
|
|||||||
|
|
||||||
identityExists := identity.ID != gid.Nil
|
identityExists := identity.ID != gid.Nil
|
||||||
if identityExists {
|
if identityExists {
|
||||||
membership := &coredata.Membership{}
|
profile := &coredata.MembershipProfile{}
|
||||||
err = membership.LoadByIdentityAndOrg(ctx, tx, scope, identity.ID, organizationID)
|
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, identity.ID, organizationID)
|
||||||
if err != nil && err != coredata.ErrResourceNotFound {
|
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 {
|
if profile.ID != gid.Nil && profile.State == coredata.ProfileStateActive {
|
||||||
return NewMembershipAlreadyExistsError(identity.ID, organizationID)
|
return NewUserAlreadyExistsError(identity.ID, organizationID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,13 +527,22 @@ func (s *OrganizationService) CreateOrganization(
|
|||||||
UpdatedAt: now,
|
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{
|
membership = &coredata.Membership{
|
||||||
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
ID: gid.New(tenantID, coredata.MembershipEntityType),
|
||||||
IdentityID: identityID,
|
IdentityID: identityID,
|
||||||
OrganizationID: organizationID,
|
OrganizationID: organizationID,
|
||||||
Role: coredata.MembershipRoleOwner,
|
Role: coredata.MembershipRoleOwner,
|
||||||
Source: coredata.MembershipSourceManual,
|
|
||||||
State: coredata.MembershipStateActive,
|
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -688,25 +675,16 @@ func (s *OrganizationService) CreateOrganization(
|
|||||||
organization.HorizontalLogoFileID = &horizontalLogoFile.ID
|
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)
|
err = profile.Insert(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot insert profile: %w", err)
|
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 {
|
if err := organizationContext.Insert(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot insert organization context: %w", err)
|
return fmt.Errorf("cannot insert organization context: %w", err)
|
||||||
}
|
}
|
||||||
@@ -1508,10 +1486,10 @@ func (s OrganizationService) DeleteSCIMConfiguration(
|
|||||||
return scim.NewSCIMConfigurationNotFoundError(configID)
|
return scim.NewSCIMConfigurationNotFoundError(configID)
|
||||||
}
|
}
|
||||||
|
|
||||||
memberships := &coredata.Memberships{}
|
profiles := &coredata.MembershipProfiles{}
|
||||||
err = memberships.ResetSCIMSources(ctx, tx, scope, config.OrganizationID)
|
err = profiles.ResetSCIMSources(ctx, tx, scope, config.OrganizationID)
|
||||||
if err != nil {
|
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
|
// 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)
|
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 {
|
func NewUserInactiveError(profileID gid.GID) error {
|
||||||
return &ErrMembershipInactive{MembershipID: membershipID}
|
return &ErrUserInactive{ProfileID: profileID}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e ErrMembershipInactive) Error() string {
|
func (e ErrUserInactive) Error() string {
|
||||||
return fmt.Sprintf("membership %q is inactive", e.MembershipID)
|
return fmt.Sprintf("user %q is inactive", e.ProfileID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -296,16 +296,23 @@ func (s *Service) HandleAssertion(
|
|||||||
|
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
|
|
||||||
err = profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, identity.ID, config.OrganizationID)
|
if err := profile.LoadByIdentityIDAndOrganizationID(
|
||||||
if err != nil && err != coredata.ErrResourceNotFound {
|
ctx,
|
||||||
return fmt.Errorf("cannot load profile: %w", err)
|
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{
|
profile = &coredata.MembershipProfile{
|
||||||
ID: gid.New(membership.ID.TenantID(), coredata.MembershipProfileEntityType),
|
ID: gid.New(membership.ID.TenantID(), coredata.MembershipProfileEntityType),
|
||||||
IdentityID: identity.ID,
|
IdentityID: identity.ID,
|
||||||
OrganizationID: config.OrganizationID,
|
OrganizationID: config.OrganizationID,
|
||||||
|
Source: coredata.ProfileSourceSAML,
|
||||||
|
State: coredata.ProfileStateActive,
|
||||||
FullName: fullname,
|
FullName: fullname,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -315,25 +322,28 @@ func (s *Service) HandleAssertion(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot insert membership profile: %w", err)
|
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 := membership.LoadByIdentityAndOrg(
|
||||||
if err != nil && err != coredata.ErrResourceNotFound {
|
ctx,
|
||||||
return fmt.Errorf("cannot load membership: %w", err)
|
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{
|
membership = &coredata.Membership{
|
||||||
ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType),
|
ID: gid.New(config.ID.TenantID(), coredata.MembershipEntityType),
|
||||||
IdentityID: identity.ID,
|
IdentityID: identity.ID,
|
||||||
OrganizationID: config.OrganizationID,
|
OrganizationID: config.OrganizationID,
|
||||||
Role: coredata.MembershipRoleEmployee,
|
Role: coredata.MembershipRoleEmployee,
|
||||||
Source: coredata.MembershipSourceSAML,
|
|
||||||
State: coredata.MembershipStateActive,
|
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -358,34 +368,26 @@ func (s *Service) HandleAssertion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.Source != coredata.MembershipSourceSCIM {
|
if profile.Source != coredata.ProfileSourceSCIM {
|
||||||
needsUpdate := false
|
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 {
|
if role != nil {
|
||||||
membership.Role = *role
|
membership.Role = *role
|
||||||
membership.UpdatedAt = now
|
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)
|
err = membership.Update(ctx, tx, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot update membership: %w", err)
|
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
|
return nil
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/mail"
|
"go.probo.inc/probo/pkg/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseUserFilter(expr scimfilter.Expression) (*coredata.MembershipFilter, error) {
|
func ParseUserFilter(expr scimfilter.Expression) (*coredata.MembershipProfileFilter, error) {
|
||||||
filter := coredata.NewMembershipFilter()
|
filter := coredata.NewMembershipProfileFilter(nil)
|
||||||
|
|
||||||
if expr == nil {
|
if expr == nil {
|
||||||
return filter, nil
|
return filter, nil
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -138,103 +139,120 @@ func (s *Service) CreateUser(
|
|||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
membershipState := coredata.MembershipStateActive
|
profileState := coredata.ProfileStateActive
|
||||||
if !active {
|
if !active {
|
||||||
membershipState = coredata.MembershipStateInactive
|
profileState = coredata.ProfileStateInactive
|
||||||
}
|
}
|
||||||
|
|
||||||
var membership *coredata.Membership
|
var membership *coredata.Membership
|
||||||
|
var profile *coredata.MembershipProfile
|
||||||
|
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
|
|
||||||
err = s.pg.WithTx(ctx, func(tx pg.Conn) error {
|
err = s.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
// Check if identity exists
|
// Check if identity exists
|
||||||
identity := &coredata.Identity{}
|
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 {
|
err = identity.Insert(ctx, tx)
|
||||||
// Create new identity
|
if err != nil {
|
||||||
identity = &coredata.Identity{
|
return fmt.Errorf("cannot insert identity: %w", err)
|
||||||
ID: gid.New(gid.NilTenant, coredata.IdentityEntityType),
|
}
|
||||||
EmailAddress: emailAddr,
|
} else {
|
||||||
FullName: fullName,
|
return fmt.Errorf("cannot load identity: %w", err)
|
||||||
HashedPassword: nil,
|
|
||||||
EmailAddressVerified: false,
|
|
||||||
CreatedAt: now,
|
|
||||||
UpdatedAt: now,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = identity.Insert(ctx, tx)
|
// Check if profile exists
|
||||||
if err != nil {
|
profile = &coredata.MembershipProfile{}
|
||||||
return fmt.Errorf("cannot insert identity: %w", err)
|
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
|
// Check if membership exists
|
||||||
membership = &coredata.Membership{}
|
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 {
|
err = membership.Insert(ctx, tx, scope)
|
||||||
// Create new membership
|
if err != nil {
|
||||||
membership = &coredata.Membership{
|
return fmt.Errorf("cannot insert membership: %w", err)
|
||||||
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)
|
// Expire all pending invitations for email in organization
|
||||||
if err != nil {
|
invitations := &coredata.Invitations{}
|
||||||
return fmt.Errorf("cannot insert membership: %w", err)
|
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||||
}
|
err := invitations.ExpireByEmailAndOrganization(
|
||||||
|
ctx,
|
||||||
|
tx,
|
||||||
|
coredata.NewScopeFromObjectID(config.OrganizationID),
|
||||||
|
emailAddr,
|
||||||
|
config.OrganizationID,
|
||||||
|
onlyPending,
|
||||||
|
)
|
||||||
|
|
||||||
// Create membership profile
|
if err != nil {
|
||||||
membershipProfile := &coredata.MembershipProfile{
|
return fmt.Errorf("cannot expire pending invitations by email")
|
||||||
ID: gid.New(membership.ID.TenantID(), coredata.MembershipProfileEntityType),
|
}
|
||||||
IdentityID: identity.ID,
|
} else {
|
||||||
OrganizationID: config.OrganizationID,
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,32 +263,45 @@ func (s *Service) CreateUser(
|
|||||||
return scim.Resource{}, err
|
return scim.Resource{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return membershipToResource(membership), nil
|
return userToResource(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetUser(
|
func (s *Service) GetUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
membershipID gid.GID,
|
profileID gid.GID,
|
||||||
) (scim.Resource, error) {
|
) (scim.Resource, error) {
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
|
|
||||||
var membership *coredata.Membership
|
var (
|
||||||
|
profile *coredata.MembershipProfile
|
||||||
|
membership *coredata.Membership
|
||||||
|
)
|
||||||
|
|
||||||
err := s.pg.WithConn(
|
err := s.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
membership = &coredata.Membership{}
|
profile = &coredata.MembershipProfile{}
|
||||||
err := membership.LoadByID(ctx, conn, scope, membershipID)
|
if err := profile.LoadByID(ctx, conn, scope, profileID); err != nil {
|
||||||
if err != nil {
|
|
||||||
if err == coredata.ErrResourceNotFound {
|
if err == coredata.ErrResourceNotFound {
|
||||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
return scimerrors.ScimErrorResourceNotFound(profileID.String())
|
||||||
}
|
}
|
||||||
return fmt.Errorf("cannot load membership: %w", err)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.OrganizationID != config.OrganizationID {
|
if profile.OrganizationID != config.OrganizationID {
|
||||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
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
|
return nil
|
||||||
@@ -281,7 +312,7 @@ func (s *Service) GetUser(
|
|||||||
return scim.Resource{}, err
|
return scim.Resource{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return membershipToResource(membership), nil
|
return userToResource(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ListUsers(
|
func (s *Service) ListUsers(
|
||||||
@@ -301,31 +332,31 @@ func (s *Service) ListUsers(
|
|||||||
// when they don't exist in the identity provider.
|
// when they don't exist in the identity provider.
|
||||||
// 2. When a manual user exists in the identity provider but not in the
|
// 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.
|
// SCIM list, CreateUser is called which enrolls them into SCIM management.
|
||||||
filter.WithSource(coredata.MembershipSourceSCIM)
|
filter.WithSource(coredata.ProfileSourceSCIM)
|
||||||
|
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
|
|
||||||
var memberships coredata.Memberships
|
var profiles coredata.MembershipProfiles
|
||||||
var totalCount int
|
var totalCount int
|
||||||
|
|
||||||
err = s.pg.WithConn(
|
err = s.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
var err 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot count memberships: %w", err)
|
return fmt.Errorf("cannot count profiles: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
orderBy := page.OrderBy[coredata.MembershipOrderField]{
|
orderBy := page.OrderBy[coredata.MembershipProfileOrderField]{
|
||||||
Field: coredata.MembershipOrderFieldCreatedAt,
|
Field: coredata.MembershipProfileOrderFieldCreatedAt,
|
||||||
Direction: page.OrderDirectionDesc,
|
Direction: page.OrderDirectionDesc,
|
||||||
}
|
}
|
||||||
cursor := page.NewCursor(count, nil, page.Head, orderBy)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot load memberships: %w", err)
|
return fmt.Errorf("cannot load profiles: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -336,9 +367,9 @@ func (s *Service) ListUsers(
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := make([]scim.Resource, 0, len(memberships))
|
resources := make([]scim.Resource, 0, len(profiles))
|
||||||
for _, m := range memberships {
|
for _, p := range profiles {
|
||||||
resources = append(resources, membershipToResource(m))
|
resources = append(resources, userToResource(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resources, totalCount, nil
|
return resources, totalCount, nil
|
||||||
@@ -347,61 +378,95 @@ func (s *Service) ListUsers(
|
|||||||
func (s *Service) ReplaceUser(
|
func (s *Service) ReplaceUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
membershipID gid.GID,
|
profileID gid.GID,
|
||||||
attributes scim.ResourceAttributes,
|
attributes scim.ResourceAttributes,
|
||||||
) (scim.Resource, error) {
|
) (scim.Resource, error) {
|
||||||
fullName, active := ParseUserFromReplaceAttributes(attributes)
|
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 {
|
if err != nil {
|
||||||
return scim.Resource{}, err
|
return scim.Resource{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return membershipToResource(membership), nil
|
return userToResource(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) PatchUser(
|
func (s *Service) PatchUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
membershipID gid.GID,
|
profileID gid.GID,
|
||||||
operations []scim.PatchOperation,
|
operations []scim.PatchOperation,
|
||||||
) (scim.Resource, error) {
|
) (scim.Resource, error) {
|
||||||
fullName, active := ParseUserFromPatchOperations(operations)
|
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 {
|
if err != nil {
|
||||||
return scim.Resource{}, err
|
return scim.Resource{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return membershipToResource(membership), nil
|
return userToResource(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) updateUser(
|
func (s *Service) updateUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
membershipID gid.GID,
|
profileID gid.GID,
|
||||||
fullName string,
|
fullName string,
|
||||||
active *bool,
|
active *bool,
|
||||||
) (*coredata.Membership, error) {
|
) (*coredata.MembershipProfile, error) {
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
var membership *coredata.Membership
|
var (
|
||||||
|
membership *coredata.Membership
|
||||||
|
profile *coredata.MembershipProfile
|
||||||
|
)
|
||||||
|
|
||||||
err := s.pg.WithTx(
|
err := s.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
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 {
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
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)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.OrganizationID != config.OrganizationID {
|
shouldReactivate := active != nil && *active && profile.State == coredata.ProfileStateInactive
|
||||||
return scimerrors.ScimErrorResourceNotFound(membershipID.String())
|
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 {
|
if active != nil {
|
||||||
identity := &coredata.Identity{}
|
identity := &coredata.Identity{}
|
||||||
@@ -409,11 +474,8 @@ func (s *Service) updateUser(
|
|||||||
return fmt.Errorf("cannot load identity: %w", err)
|
return fmt.Errorf("cannot load identity: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *active && membership.State == coredata.MembershipStateInactive {
|
if shouldReactivate {
|
||||||
membership.State = coredata.MembershipStateActive
|
|
||||||
membership.Role = coredata.MembershipRoleEmployee
|
membership.Role = coredata.MembershipRoleEmployee
|
||||||
needsUpdate = true
|
|
||||||
|
|
||||||
// Expire all pending invitations for email in organization
|
// Expire all pending invitations for email in organization
|
||||||
invitations := &coredata.Invitations{}
|
invitations := &coredata.Invitations{}
|
||||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
||||||
@@ -427,10 +489,7 @@ func (s *Service) updateUser(
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("cannot expire pending invitations by email: %w", err)
|
return fmt.Errorf("cannot expire pending invitations by email: %w", err)
|
||||||
}
|
}
|
||||||
} else if !*active && membership.State == coredata.MembershipStateActive {
|
} else if shouldDeactivate {
|
||||||
membership.State = coredata.MembershipStateInactive
|
|
||||||
needsUpdate = true
|
|
||||||
|
|
||||||
// Expire all pending invitations for email in organization
|
// Expire all pending invitations for email in organization
|
||||||
invitations := &coredata.Invitations{}
|
invitations := &coredata.Invitations{}
|
||||||
onlyPending := coredata.NewInvitationFilter([]coredata.InvitationStatus{coredata.InvitationStatusPending})
|
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 {
|
if needsUpdate {
|
||||||
membership.UpdatedAt = now
|
membership.UpdatedAt = now
|
||||||
if err := membership.Update(ctx, tx, scope); err != nil {
|
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
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -479,7 +521,7 @@ func (s *Service) updateUser(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return membership, nil
|
return profile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DeleteUser(
|
func (s *Service) DeleteUser(
|
||||||
@@ -538,19 +580,14 @@ func (s *Service) LogEvent(
|
|||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
method string,
|
method string,
|
||||||
path string,
|
path string,
|
||||||
membershipID *gid.GID,
|
userName string,
|
||||||
ipAddress net.IP,
|
ipAddress net.IP,
|
||||||
statusCode int,
|
statusCode int,
|
||||||
errorMessage *string,
|
errorMessage *string,
|
||||||
) {
|
) {
|
||||||
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
scope := coredata.NewScopeFromObjectID(config.OrganizationID)
|
||||||
|
|
||||||
var mID gid.GID
|
event := s.createEvent(config, method, path, userName, ipAddress, statusCode, errorMessage)
|
||||||
if membershipID != nil {
|
|
||||||
mID = *membershipID
|
|
||||||
}
|
|
||||||
|
|
||||||
event := s.createEvent(config, method, path, mID, ipAddress, statusCode, errorMessage)
|
|
||||||
|
|
||||||
err := s.pg.WithConn(
|
err := s.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -572,7 +609,7 @@ func (s *Service) createEvent(
|
|||||||
config *coredata.SCIMConfiguration,
|
config *coredata.SCIMConfiguration,
|
||||||
method string,
|
method string,
|
||||||
path string,
|
path string,
|
||||||
membershipID gid.GID,
|
userName string,
|
||||||
ipAddress net.IP,
|
ipAddress net.IP,
|
||||||
statusCode int,
|
statusCode int,
|
||||||
errorMessage *string,
|
errorMessage *string,
|
||||||
@@ -586,13 +623,10 @@ func (s *Service) createEvent(
|
|||||||
StatusCode: statusCode,
|
StatusCode: statusCode,
|
||||||
ErrorMessage: errorMessage,
|
ErrorMessage: errorMessage,
|
||||||
IPAddress: ipAddress,
|
IPAddress: ipAddress,
|
||||||
|
UserName: userName,
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if membershipID != gid.Nil {
|
|
||||||
event.MembershipID = &membershipID
|
|
||||||
}
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,28 +764,28 @@ func ParseUserFromPatchOperations(operations []scim.PatchOperation) (fullName st
|
|||||||
return fullName, active
|
return fullName, active
|
||||||
}
|
}
|
||||||
|
|
||||||
func membershipToResource(m *coredata.Membership) scim.Resource {
|
func userToResource(p *coredata.MembershipProfile) scim.Resource {
|
||||||
return scim.Resource{
|
return scim.Resource{
|
||||||
ID: m.ID.String(),
|
ID: p.ID.String(),
|
||||||
ExternalID: optional.NewString(m.ID.String()),
|
ExternalID: optional.NewString(p.ID.String()),
|
||||||
Attributes: scim.ResourceAttributes{
|
Attributes: scim.ResourceAttributes{
|
||||||
"userName": m.EmailAddress.String(),
|
"userName": p.EmailAddress.String(),
|
||||||
"displayName": m.FullName,
|
"displayName": p.FullName,
|
||||||
"active": m.State == coredata.MembershipStateActive,
|
"active": p.State == coredata.ProfileStateActive,
|
||||||
"name": map[string]any{
|
"name": map[string]any{
|
||||||
"formatted": m.FullName,
|
"formatted": p.FullName,
|
||||||
},
|
},
|
||||||
"emails": []map[string]any{
|
"emails": []map[string]any{
|
||||||
{
|
{
|
||||||
"value": m.EmailAddress.String(),
|
"value": p.EmailAddress.String(),
|
||||||
"type": "work",
|
"type": "work",
|
||||||
"primary": true,
|
"primary": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Meta: scim.Meta{
|
Meta: scim.Meta{
|
||||||
Created: &m.CreatedAt,
|
Created: &p.CreatedAt,
|
||||||
LastModified: &m.UpdatedAt,
|
LastModified: &p.UpdatedAt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,6 +324,7 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
|||||||
now = time.Now()
|
now = time.Now()
|
||||||
rootSession = &coredata.Session{}
|
rootSession = &coredata.Session{}
|
||||||
identity = &coredata.Identity{}
|
identity = &coredata.Identity{}
|
||||||
|
profile = &coredata.MembershipProfile{}
|
||||||
membership = &coredata.Membership{}
|
membership = &coredata.Membership{}
|
||||||
childSession = &coredata.Session{}
|
childSession = &coredata.Session{}
|
||||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||||
@@ -353,6 +354,18 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load identity: %w", err)
|
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)
|
err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == coredata.ErrResourceNotFound {
|
if err == coredata.ErrResourceNotFound {
|
||||||
@@ -361,10 +374,6 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load membership: %w", err)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.State == coredata.MembershipStateInactive {
|
|
||||||
return NewMembershipInactiveError(membership.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
tenantID := scope.GetTenantID()
|
tenantID := scope.GetTenantID()
|
||||||
childSession = &coredata.Session{
|
childSession = &coredata.Session{
|
||||||
ID: gid.New(tenantID, coredata.SessionEntityType),
|
ID: gid.New(tenantID, coredata.SessionEntityType),
|
||||||
@@ -417,6 +426,7 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
|||||||
now = time.Now()
|
now = time.Now()
|
||||||
rootSession = &coredata.Session{}
|
rootSession = &coredata.Session{}
|
||||||
identity = &coredata.Identity{}
|
identity = &coredata.Identity{}
|
||||||
|
profile = &coredata.MembershipProfile{}
|
||||||
membership = &coredata.Membership{}
|
membership = &coredata.Membership{}
|
||||||
childSession = &coredata.Session{}
|
childSession = &coredata.Session{}
|
||||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||||
@@ -446,6 +456,18 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load identity: %w", err)
|
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)
|
err = membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == coredata.ErrResourceNotFound {
|
if err == coredata.ErrResourceNotFound {
|
||||||
@@ -454,10 +476,6 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load membership: %w", err)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.State == coredata.MembershipStateInactive {
|
|
||||||
return NewMembershipInactiveError(membership.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
tenantID := scope.GetTenantID()
|
tenantID := scope.GetTenantID()
|
||||||
childSession = &coredata.Session{
|
childSession = &coredata.Session{
|
||||||
ID: gid.New(tenantID, coredata.SessionEntityType),
|
ID: gid.New(tenantID, coredata.SessionEntityType),
|
||||||
@@ -497,6 +515,7 @@ func (s SessionService) AssumeOrganizationSession(
|
|||||||
now = time.Now()
|
now = time.Now()
|
||||||
rootSession = &coredata.Session{}
|
rootSession = &coredata.Session{}
|
||||||
identity = &coredata.Identity{}
|
identity = &coredata.Identity{}
|
||||||
|
profile = &coredata.MembershipProfile{}
|
||||||
membership = &coredata.Membership{}
|
membership = &coredata.Membership{}
|
||||||
childSession = &coredata.Session{}
|
childSession = &coredata.Session{}
|
||||||
scope = coredata.NewScopeFromObjectID(organizationID)
|
scope = coredata.NewScopeFromObjectID(organizationID)
|
||||||
@@ -524,6 +543,17 @@ func (s SessionService) AssumeOrganizationSession(
|
|||||||
return fmt.Errorf("cannot load identity: %w", err)
|
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 := membership.LoadByIdentityInOrganization(ctx, tx, rootSession.IdentityID, organizationID); err != nil {
|
||||||
if err == coredata.ErrResourceNotFound {
|
if err == coredata.ErrResourceNotFound {
|
||||||
return NewMembershipNotFoundError(organizationID)
|
return NewMembershipNotFoundError(organizationID)
|
||||||
@@ -531,10 +561,6 @@ func (s SessionService) AssumeOrganizationSession(
|
|||||||
return fmt.Errorf("cannot load membership: %w", err)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if membership.State == coredata.MembershipStateInactive {
|
|
||||||
return NewMembershipInactiveError(membership.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
samlConfig := &coredata.SAMLConfiguration{}
|
samlConfig := &coredata.SAMLConfiguration{}
|
||||||
err := samlConfig.LoadByOrganizationIDAndEmailDomain(
|
err := samlConfig.LoadByOrganizationIDAndEmailDomain(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -181,6 +181,8 @@ type Identity implements Node {
|
|||||||
type Profile implements Node {
|
type Profile implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
fullName: String!
|
fullName: String!
|
||||||
|
source: String!
|
||||||
|
state: ProfileState!
|
||||||
additionalEmailAddresses: [EmailAddr!]!
|
additionalEmailAddresses: [EmailAddr!]!
|
||||||
kind: ProfileKind!
|
kind: ProfileKind!
|
||||||
position: String
|
position: String
|
||||||
@@ -198,6 +200,32 @@ type Profile implements Node {
|
|||||||
@session(required: PRESENT)
|
@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 {
|
type Organization implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
@@ -243,17 +271,6 @@ type Organization implements Node {
|
|||||||
@session(required: PRESENT)
|
@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
|
enum MembershipRole
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
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")
|
@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 {
|
type Membership implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
role: MembershipRole!
|
role: MembershipRole!
|
||||||
source: MembershipSource!
|
|
||||||
state: MembershipState!
|
|
||||||
|
|
||||||
lastSession: Session @goField(forceResolver: true)
|
lastSession: Session @goField(forceResolver: true)
|
||||||
|
|
||||||
@@ -440,7 +440,7 @@ type SCIMEvent implements Node {
|
|||||||
requestBody: String
|
requestBody: String
|
||||||
responseBody: String
|
responseBody: String
|
||||||
errorMessage: String
|
errorMessage: String
|
||||||
profile: Profile @goField(forceResolver: true)
|
userName: String!
|
||||||
ipAddress: String!
|
ipAddress: String!
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
|
|
||||||
|
|||||||
@@ -192,8 +192,6 @@ type ComplexityRoot struct {
|
|||||||
LastSession func(childComplexity int) int
|
LastSession func(childComplexity int) int
|
||||||
Permission func(childComplexity int, action string) int
|
Permission func(childComplexity int, action string) int
|
||||||
Role func(childComplexity int) int
|
Role func(childComplexity int) int
|
||||||
Source func(childComplexity int) int
|
|
||||||
State func(childComplexity int) int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutation struct {
|
Mutation struct {
|
||||||
@@ -299,6 +297,8 @@ type ComplexityRoot struct {
|
|||||||
Organization func(childComplexity int) int
|
Organization func(childComplexity int) int
|
||||||
Permission func(childComplexity int, action string) int
|
Permission func(childComplexity int, action string) int
|
||||||
Position func(childComplexity int) int
|
Position func(childComplexity int) int
|
||||||
|
Source func(childComplexity int) int
|
||||||
|
State func(childComplexity int) int
|
||||||
UpdatedAt func(childComplexity int) int
|
UpdatedAt func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,10 +414,10 @@ type ComplexityRoot struct {
|
|||||||
Method func(childComplexity int) int
|
Method func(childComplexity int) int
|
||||||
Path func(childComplexity int) int
|
Path func(childComplexity int) int
|
||||||
Permission func(childComplexity int, action string) int
|
Permission func(childComplexity int, action string) int
|
||||||
Profile func(childComplexity int) int
|
|
||||||
RequestBody func(childComplexity int) int
|
RequestBody func(childComplexity int) int
|
||||||
ResponseBody func(childComplexity int) int
|
ResponseBody func(childComplexity int) int
|
||||||
StatusCode func(childComplexity int) int
|
StatusCode func(childComplexity int) int
|
||||||
|
UserName func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
SCIMEventConnection struct {
|
SCIMEventConnection struct {
|
||||||
@@ -605,8 +605,6 @@ type SCIMConfigurationResolver interface {
|
|||||||
Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error)
|
Permission(ctx context.Context, obj *types.SCIMConfiguration, action string) (bool, error)
|
||||||
}
|
}
|
||||||
type SCIMEventResolver interface {
|
type SCIMEventResolver interface {
|
||||||
Profile(ctx context.Context, obj *types.SCIMEvent) (*types.Profile, error)
|
|
||||||
|
|
||||||
Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error)
|
Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error)
|
||||||
}
|
}
|
||||||
type SCIMEventConnectionResolver interface {
|
type SCIMEventConnectionResolver interface {
|
||||||
@@ -1042,18 +1040,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.Membership.Role(childComplexity), true
|
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":
|
case "Mutation.acceptInvitation":
|
||||||
if e.complexity.Mutation.AcceptInvitation == nil {
|
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
|
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":
|
case "Profile.updatedAt":
|
||||||
if e.complexity.Profile.UpdatedAt == nil {
|
if e.complexity.Profile.UpdatedAt == nil {
|
||||||
break
|
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
|
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":
|
case "SCIMEvent.requestBody":
|
||||||
if e.complexity.SCIMEvent.RequestBody == nil {
|
if e.complexity.SCIMEvent.RequestBody == nil {
|
||||||
break
|
break
|
||||||
@@ -2166,6 +2158,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
return e.complexity.SCIMEvent.StatusCode(childComplexity), true
|
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":
|
case "SCIMEventConnection.edges":
|
||||||
if e.complexity.SCIMEventConnection.Edges == nil {
|
if e.complexity.SCIMEventConnection.Edges == nil {
|
||||||
@@ -2683,6 +2681,8 @@ type Identity implements Node {
|
|||||||
type Profile implements Node {
|
type Profile implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
fullName: String!
|
fullName: String!
|
||||||
|
source: String!
|
||||||
|
state: ProfileState!
|
||||||
additionalEmailAddresses: [EmailAddr!]!
|
additionalEmailAddresses: [EmailAddr!]!
|
||||||
kind: ProfileKind!
|
kind: ProfileKind!
|
||||||
position: String
|
position: String
|
||||||
@@ -2700,6 +2700,32 @@ type Profile implements Node {
|
|||||||
@session(required: PRESENT)
|
@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 {
|
type Organization implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
@@ -2745,17 +2771,6 @@ type Organization implements Node {
|
|||||||
@session(required: PRESENT)
|
@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
|
enum MembershipRole
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
|
||||||
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
|
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")
|
@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 {
|
type Membership implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
role: MembershipRole!
|
role: MembershipRole!
|
||||||
source: MembershipSource!
|
|
||||||
state: MembershipState!
|
|
||||||
|
|
||||||
lastSession: Session @goField(forceResolver: true)
|
lastSession: Session @goField(forceResolver: true)
|
||||||
|
|
||||||
@@ -2942,7 +2940,7 @@ type SCIMEvent implements Node {
|
|||||||
requestBody: String
|
requestBody: String
|
||||||
responseBody: String
|
responseBody: String
|
||||||
errorMessage: String
|
errorMessage: String
|
||||||
profile: Profile @goField(forceResolver: true)
|
userName: String!
|
||||||
ipAddress: String!
|
ipAddress: String!
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
|
|
||||||
@@ -4335,10 +4333,6 @@ func (ec *executionContext) fieldContext_AcceptInvitationPayload_membership(_ co
|
|||||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
case "role":
|
case "role":
|
||||||
return ec.fieldContext_Membership_role(ctx, field)
|
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":
|
case "lastSession":
|
||||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
case "permission":
|
case "permission":
|
||||||
@@ -4756,10 +4750,6 @@ func (ec *executionContext) fieldContext_CreateOrganizationPayload_membership(_
|
|||||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
case "role":
|
case "role":
|
||||||
return ec.fieldContext_Membership_role(ctx, field)
|
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":
|
case "lastSession":
|
||||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
case "permission":
|
case "permission":
|
||||||
@@ -6338,64 +6328,6 @@ func (ec *executionContext) fieldContext_Membership_role(_ context.Context, fiel
|
|||||||
return fc, nil
|
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) {
|
func (ec *executionContext) _Membership_lastSession(ctx context.Context, field graphql.CollectedField, obj *types.Membership) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -8921,6 +8853,10 @@ func (ec *executionContext) fieldContext_Organization_viewer(_ context.Context,
|
|||||||
return ec.fieldContext_Profile_id(ctx, field)
|
return ec.fieldContext_Profile_id(ctx, field)
|
||||||
case "fullName":
|
case "fullName":
|
||||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
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":
|
case "additionalEmailAddresses":
|
||||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||||
case "kind":
|
case "kind":
|
||||||
@@ -9086,10 +9022,6 @@ func (ec *executionContext) fieldContext_OrganizationSessionCreated_membership(_
|
|||||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
case "role":
|
case "role":
|
||||||
return ec.fieldContext_Membership_role(ctx, field)
|
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":
|
case "lastSession":
|
||||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
case "permission":
|
case "permission":
|
||||||
@@ -9714,6 +9646,64 @@ func (ec *executionContext) fieldContext_Profile_fullName(_ context.Context, fie
|
|||||||
return fc, nil
|
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) {
|
func (ec *executionContext) _Profile_additionalEmailAddresses(ctx context.Context, field graphql.CollectedField, obj *types.Profile) (ret graphql.Marshaler) {
|
||||||
return graphql.ResolveField(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -10063,10 +10053,6 @@ func (ec *executionContext) fieldContext_Profile_membership(_ context.Context, f
|
|||||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
case "role":
|
case "role":
|
||||||
return ec.fieldContext_Membership_role(ctx, field)
|
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":
|
case "lastSession":
|
||||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
case "permission":
|
case "permission":
|
||||||
@@ -10297,6 +10283,10 @@ func (ec *executionContext) fieldContext_ProfileEdge_node(_ context.Context, fie
|
|||||||
return ec.fieldContext_Profile_id(ctx, field)
|
return ec.fieldContext_Profile_id(ctx, field)
|
||||||
case "fullName":
|
case "fullName":
|
||||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
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":
|
case "additionalEmailAddresses":
|
||||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||||
case "kind":
|
case "kind":
|
||||||
@@ -12488,58 +12478,30 @@ func (ec *executionContext) fieldContext_SCIMEvent_errorMessage(_ context.Contex
|
|||||||
return fc, nil
|
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(
|
return graphql.ResolveField(
|
||||||
ctx,
|
ctx,
|
||||||
ec.OperationContext,
|
ec.OperationContext,
|
||||||
field,
|
field,
|
||||||
ec.fieldContext_SCIMEvent_profile,
|
ec.fieldContext_SCIMEvent_userName,
|
||||||
func(ctx context.Context) (any, error) {
|
func(ctx context.Context) (any, error) {
|
||||||
return ec.resolvers.SCIMEvent().Profile(ctx, obj)
|
return obj.UserName, nil
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
ec.marshalOProfile2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐProfile,
|
ec.marshalNString2string,
|
||||||
|
true,
|
||||||
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{
|
fc = &graphql.FieldContext{
|
||||||
Object: "SCIMEvent",
|
Object: "SCIMEvent",
|
||||||
Field: field,
|
Field: field,
|
||||||
IsMethod: true,
|
IsMethod: false,
|
||||||
IsResolver: true,
|
IsResolver: false,
|
||||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
switch field.Name {
|
return nil, errors.New("field of type String does not have child fields")
|
||||||
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 fc, nil
|
return fc, nil
|
||||||
@@ -12803,8 +12765,8 @@ func (ec *executionContext) fieldContext_SCIMEventEdge_node(_ context.Context, f
|
|||||||
return ec.fieldContext_SCIMEvent_responseBody(ctx, field)
|
return ec.fieldContext_SCIMEvent_responseBody(ctx, field)
|
||||||
case "errorMessage":
|
case "errorMessage":
|
||||||
return ec.fieldContext_SCIMEvent_errorMessage(ctx, field)
|
return ec.fieldContext_SCIMEvent_errorMessage(ctx, field)
|
||||||
case "profile":
|
case "userName":
|
||||||
return ec.fieldContext_SCIMEvent_profile(ctx, field)
|
return ec.fieldContext_SCIMEvent_userName(ctx, field)
|
||||||
case "ipAddress":
|
case "ipAddress":
|
||||||
return ec.fieldContext_SCIMEvent_ipAddress(ctx, field)
|
return ec.fieldContext_SCIMEvent_ipAddress(ctx, field)
|
||||||
case "createdAt":
|
case "createdAt":
|
||||||
@@ -13585,10 +13547,6 @@ func (ec *executionContext) fieldContext_UpdateMembershipPayload_membership(_ co
|
|||||||
return ec.fieldContext_Membership_createdAt(ctx, field)
|
return ec.fieldContext_Membership_createdAt(ctx, field)
|
||||||
case "role":
|
case "role":
|
||||||
return ec.fieldContext_Membership_role(ctx, field)
|
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":
|
case "lastSession":
|
||||||
return ec.fieldContext_Membership_lastSession(ctx, field)
|
return ec.fieldContext_Membership_lastSession(ctx, field)
|
||||||
case "permission":
|
case "permission":
|
||||||
@@ -13691,6 +13649,10 @@ func (ec *executionContext) fieldContext_UpdateProfilePayload_profile(_ context.
|
|||||||
return ec.fieldContext_Profile_id(ctx, field)
|
return ec.fieldContext_Profile_id(ctx, field)
|
||||||
case "fullName":
|
case "fullName":
|
||||||
return ec.fieldContext_Profile_fullName(ctx, field)
|
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":
|
case "additionalEmailAddresses":
|
||||||
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field)
|
||||||
case "kind":
|
case "kind":
|
||||||
@@ -18019,16 +17981,6 @@ func (ec *executionContext) _Membership(ctx context.Context, sel ast.SelectionSe
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
atomic.AddUint32(&out.Invalids, 1)
|
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":
|
case "lastSession":
|
||||||
field := field
|
field := field
|
||||||
|
|
||||||
@@ -19023,6 +18975,16 @@ func (ec *executionContext) _Profile(ctx context.Context, sel ast.SelectionSet,
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
atomic.AddUint32(&out.Invalids, 1)
|
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":
|
case "additionalEmailAddresses":
|
||||||
out.Values[i] = ec._Profile_additionalEmailAddresses(ctx, field, obj)
|
out.Values[i] = ec._Profile_additionalEmailAddresses(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
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)
|
out.Values[i] = ec._SCIMEvent_responseBody(ctx, field, obj)
|
||||||
case "errorMessage":
|
case "errorMessage":
|
||||||
out.Values[i] = ec._SCIMEvent_errorMessage(ctx, field, obj)
|
out.Values[i] = ec._SCIMEvent_errorMessage(ctx, field, obj)
|
||||||
case "profile":
|
case "userName":
|
||||||
field := field
|
out.Values[i] = ec._SCIMEvent_userName(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
|
atomic.AddUint32(&out.Invalids, 1)
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
ec.Error(ctx, ec.Recover(ctx, r))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
res = ec._SCIMEvent_profile(ctx, field, obj)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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":
|
case "ipAddress":
|
||||||
out.Values[i] = ec._SCIMEvent_ipAddress(ctx, field, obj)
|
out.Values[i] = ec._SCIMEvent_ipAddress(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
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) {
|
func (ec *executionContext) unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx context.Context, v any) (page.OrderDirection, error) {
|
||||||
tmp, err := graphql.UnmarshalString(v)
|
tmp, err := graphql.UnmarshalString(v)
|
||||||
res := unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection[tmp]
|
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) {
|
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
|
var res types.ReauthenticationReason
|
||||||
err := res.UnmarshalGQL(v)
|
err := res.UnmarshalGQL(v)
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ type (
|
|||||||
}
|
}
|
||||||
|
|
||||||
scimRequestContext struct {
|
scimRequestContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
config *coredata.SCIMConfiguration
|
config *coredata.SCIMConfiguration
|
||||||
ipAddress net.IP
|
ipAddress net.IP
|
||||||
method string
|
method string
|
||||||
path string
|
path string
|
||||||
membershipID *gid.GID
|
userName string
|
||||||
handler *scimResourceHandler
|
handler *scimResourceHandler
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -145,23 +145,23 @@ func (rc *scimRequestContext) logAndWrapError(err error, logMsg string) error {
|
|||||||
if errors.As(err, &scimErr) {
|
if errors.As(err, &scimErr) {
|
||||||
errMsg := scimErr.Detail
|
errMsg := scimErr.Detail
|
||||||
|
|
||||||
// Don't reference membershipID for 404 errors - the resource doesn't exist
|
// Don't reference profileID for 404 errors - the resource doesn't exist
|
||||||
membershipID := rc.membershipID
|
userName := rc.userName
|
||||||
if scimErr.Status == http.StatusNotFound {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.handler.handler.logger.ErrorCtx(rc.ctx, logMsg, log.Error(err))
|
rc.handler.handler.logger.ErrorCtx(rc.ctx, logMsg, log.Error(err))
|
||||||
errMsg := "internal server error"
|
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
|
return scimerrors.ScimErrorInternal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *scimRequestContext) logSuccess(statusCode int) {
|
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) {
|
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")
|
return scim.Resource{}, rc.logAndWrapError(err, "cannot create user")
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipID, _ := gid.ParseGID(resource.ID)
|
rc.userName = resource.Attributes["userName"].(string)
|
||||||
rc.membershipID = &membershipID
|
|
||||||
rc.logSuccess(201)
|
rc.logSuccess(201)
|
||||||
|
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,18 +196,20 @@ func (h *scimResourceHandler) Get(r *http.Request, id string) (scim.Resource, er
|
|||||||
handler: h,
|
handler: h,
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipID, err := gid.ParseGID(id)
|
profileID, err := gid.ParseGID(id)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot get user")
|
return scim.Resource{}, rc.logAndWrapError(err, "cannot get user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc.userName = resource.Attributes["userName"].(string)
|
||||||
|
|
||||||
rc.logSuccess(200)
|
rc.logSuccess(200)
|
||||||
|
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,18 +259,20 @@ func (h *scimResourceHandler) Replace(r *http.Request, id string, attributes sci
|
|||||||
handler: h,
|
handler: h,
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipID, err := gid.ParseGID(id)
|
profileID, err := gid.ParseGID(id)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot update user")
|
return scim.Resource{}, rc.logAndWrapError(err, "cannot update user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc.userName = resource.Attributes["userName"].(string)
|
||||||
|
|
||||||
rc.logSuccess(200)
|
rc.logSuccess(200)
|
||||||
|
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,18 +286,20 @@ func (h *scimResourceHandler) Patch(r *http.Request, id string, operations []sci
|
|||||||
handler: h,
|
handler: h,
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipID, err := gid.ParseGID(id)
|
profileID, err := gid.ParseGID(id)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return scim.Resource{}, rc.logAndWrapError(err, "cannot patch user")
|
return scim.Resource{}, rc.logAndWrapError(err, "cannot patch user")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rc.userName = resource.Attributes["userName"].(string)
|
||||||
|
|
||||||
rc.logSuccess(200)
|
rc.logSuccess(200)
|
||||||
|
|
||||||
return resource, nil
|
return resource, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,19 +313,20 @@ func (h *scimResourceHandler) Delete(r *http.Request, id string) error {
|
|||||||
handler: h,
|
handler: h,
|
||||||
}
|
}
|
||||||
|
|
||||||
membershipID, err := gid.ParseGID(id)
|
profileID, err := gid.ParseGID(id)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return rc.logAndWrapError(err, "cannot delete user")
|
return rc.logAndWrapError(err, "cannot delete user")
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.membershipID = nil
|
rc.userName = ""
|
||||||
|
|
||||||
rc.logSuccess(204)
|
rc.logSuccess(204)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,5 @@ func NewMembership(membership *coredata.Membership) *Membership {
|
|||||||
ID: membership.ID,
|
ID: membership.ID,
|
||||||
CreatedAt: membership.CreatedAt,
|
CreatedAt: membership.CreatedAt,
|
||||||
Role: membership.Role,
|
Role: membership.Role,
|
||||||
Source: membership.Source,
|
|
||||||
State: membership.State,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ func NewProfile(profile *coredata.MembershipProfile) *Profile {
|
|||||||
return &Profile{
|
return &Profile{
|
||||||
ID: profile.ID,
|
ID: profile.ID,
|
||||||
FullName: profile.FullName,
|
FullName: profile.FullName,
|
||||||
|
Source: profile.Source.String(),
|
||||||
|
State: profile.State,
|
||||||
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
|
AdditionalEmailAddresses: profile.AdditionalEmailAddresses,
|
||||||
Kind: profile.Kind,
|
Kind: profile.Kind,
|
||||||
Position: profile.Position,
|
Position: profile.Position,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func NewSCIMEvent(scimEvent *coredata.SCIMEvent) *SCIMEvent {
|
|||||||
ID: scimEvent.ID,
|
ID: scimEvent.ID,
|
||||||
Method: scimEvent.Method,
|
Method: scimEvent.Method,
|
||||||
Path: scimEvent.Path,
|
Path: scimEvent.Path,
|
||||||
|
UserName: scimEvent.UserName,
|
||||||
StatusCode: scimEvent.StatusCode,
|
StatusCode: scimEvent.StatusCode,
|
||||||
RequestBody: scimEvent.RequestBody,
|
RequestBody: scimEvent.RequestBody,
|
||||||
ResponseBody: scimEvent.ResponseBody,
|
ResponseBody: scimEvent.ResponseBody,
|
||||||
@@ -72,12 +73,5 @@ func NewSCIMEvent(scimEvent *coredata.SCIMEvent) *SCIMEvent {
|
|||||||
CreatedAt: scimEvent.CreatedAt,
|
CreatedAt: scimEvent.CreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: profile
|
|
||||||
// if scimEvent.MembershipID != nil {
|
|
||||||
// event.Membership = &Membership{
|
|
||||||
// ID: *scimEvent.MembershipID,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,13 +220,11 @@ type InviteMemberPayload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Membership struct {
|
type Membership struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Role coredata.MembershipRole `json:"role"`
|
Role coredata.MembershipRole `json:"role"`
|
||||||
Source coredata.MembershipSource `json:"source"`
|
LastSession *Session `json:"lastSession,omitempty"`
|
||||||
State coredata.MembershipState `json:"state"`
|
Permission bool `json:"permission"`
|
||||||
LastSession *Session `json:"lastSession,omitempty"`
|
|
||||||
Permission bool `json:"permission"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Membership) IsNode() {}
|
func (Membership) IsNode() {}
|
||||||
@@ -298,6 +296,8 @@ type PersonalAPIKeyEdge struct {
|
|||||||
type Profile struct {
|
type Profile struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
FullName string `json:"fullName"`
|
FullName string `json:"fullName"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
State coredata.ProfileState `json:"state"`
|
||||||
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
|
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
|
||||||
Kind coredata.MembershipProfileKind `json:"kind"`
|
Kind coredata.MembershipProfileKind `json:"kind"`
|
||||||
Position *string `json:"position,omitempty"`
|
Position *string `json:"position,omitempty"`
|
||||||
@@ -452,7 +452,7 @@ type SCIMEvent struct {
|
|||||||
RequestBody *string `json:"requestBody,omitempty"`
|
RequestBody *string `json:"requestBody,omitempty"`
|
||||||
ResponseBody *string `json:"responseBody,omitempty"`
|
ResponseBody *string `json:"responseBody,omitempty"`
|
||||||
ErrorMessage *string `json:"errorMessage,omitempty"`
|
ErrorMessage *string `json:"errorMessage,omitempty"`
|
||||||
Profile *Profile `json:"profile,omitempty"`
|
UserName string `json:"userName"`
|
||||||
IPAddress string `json:"ipAddress"`
|
IPAddress string `json:"ipAddress"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Permission bool `json:"permission"`
|
Permission bool `json:"permission"`
|
||||||
|
|||||||
@@ -360,9 +360,9 @@ func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
// Here session middleware already took care of expired/nil root session so we only handle membership related errors
|
||||||
var errMembershipNotFound *iam.ErrMembershipNotFound
|
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")
|
return nil, gqlutils.Forbiddenf(ctx, "forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,13 +890,13 @@ func (r *mutationResolver) InviteMember(ctx context.Context, input types.InviteM
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
var errOrganizationNotFound *iam.ErrOrganizationNotFound
|
||||||
var errMembershipAlreadyExists *iam.ErrMembershipAlreadyExists
|
var errUserAlreadyExists *iam.ErrUserAlreadyExists
|
||||||
|
|
||||||
if errors.As(err, &errOrganizationNotFound) {
|
if errors.As(err, &errOrganizationNotFound) {
|
||||||
return nil, gqlutils.NotFound(ctx, err)
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.As(err, &errMembershipAlreadyExists) {
|
if errors.As(err, &errUserAlreadyExists) {
|
||||||
return nil, gqlutils.Conflict(ctx, err)
|
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)
|
err := r.iam.OrganizationService.RemoveMember(ctx, input.OrganizationID, input.MembershipID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var errManagedBySCIM *iam.ErrMembershipManagedBySCIM
|
var errManagedBySCIM *iam.ErrUserManagedBySCIM
|
||||||
var errLastActiveOwner *iam.ErrLastActiveOwner
|
var errLastActiveOwner *iam.ErrLastActiveOwner
|
||||||
|
|
||||||
if errors.As(err, &errManagedBySCIM) {
|
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)
|
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.
|
// Permission is the resolver for the permission field.
|
||||||
func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) {
|
func (r *sCIMEventResolver) Permission(ctx context.Context, obj *types.SCIMEvent, action string) (bool, error) {
|
||||||
return r.Resolver.Permission(ctx, obj, action)
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
|
|||||||
Reference in New Issue
Block a user