Add access review dependencies and enum tests
Add go-vcr dependency, dev config for new providers, connector service changes for access review, connect schema updates, and unit tests for enum Scan/Value. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,3 +18,4 @@ compose/keycloak/probo-realm.json
|
|||||||
# Generated files (codegen)
|
# Generated files (codegen)
|
||||||
__generated__/
|
__generated__/
|
||||||
pkg/server/api/*/v1/types/types.go
|
pkg/server/api/*/v1/types/types.go
|
||||||
|
cfg/dev_local.yaml
|
||||||
|
|||||||
105
cfg/dev.yaml
105
cfg/dev.yaml
@@ -109,29 +109,124 @@ probod:
|
|||||||
- provider: "SLACK"
|
- provider: "SLACK"
|
||||||
protocol: "oauth2"
|
protocol: "oauth2"
|
||||||
config:
|
config:
|
||||||
client-id: "slack-client-id"
|
client-id: "your-slack-client-id"
|
||||||
client-secret: "thisisnotasecret"
|
client-secret: "your-slack-client-secret"
|
||||||
redirect-uri: "https://localhost:8080/api/console/v1/connectors/complete"
|
redirect-uri: "https://localhost:8080/api/console/v1/connectors/complete"
|
||||||
auth-url: "https://slack.com/oauth/v2/authorize"
|
auth-url: "https://slack.com/oauth/v2/authorize"
|
||||||
token-url: "https://slack.com/api/oauth.v2.access"
|
token-url: "https://slack.com/api/oauth.v2.access"
|
||||||
|
|
||||||
scopes:
|
scopes:
|
||||||
- "chat:write"
|
- "chat:write"
|
||||||
- "channels:join"
|
- "channels:join"
|
||||||
- "incoming-webhook"
|
- "incoming-webhook"
|
||||||
|
- "users:read"
|
||||||
|
- "users:read.email"
|
||||||
settings:
|
settings:
|
||||||
signing-secret: "this-is-not-a-secret-for-slack-signing"
|
signing-secret: "your-slack-signing-secret"
|
||||||
- provider: "GOOGLE_WORKSPACE"
|
- provider: "GOOGLE_WORKSPACE"
|
||||||
protocol: "oauth2"
|
protocol: "oauth2"
|
||||||
config:
|
config:
|
||||||
client-id: "google-workspace-client-id"
|
client-id: "your-google-client-id.apps.googleusercontent.com"
|
||||||
client-secret: "thisisnotasecret"
|
client-secret: "your-google-client-secret"
|
||||||
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
auth-url: "https://accounts.google.com/o/oauth2/v2/auth"
|
auth-url: "https://accounts.google.com/o/oauth2/v2/auth"
|
||||||
token-url: "https://oauth2.googleapis.com/token"
|
token-url: "https://oauth2.googleapis.com/token"
|
||||||
|
|
||||||
scopes:
|
scopes:
|
||||||
- "https://www.googleapis.com/auth/admin.directory.user.readonly"
|
- "https://www.googleapis.com/auth/admin.directory.user.readonly"
|
||||||
- "https://www.googleapis.com/auth/admin.directory.userschema.readonly"
|
- "https://www.googleapis.com/auth/admin.directory.userschema.readonly"
|
||||||
- "https://www.googleapis.com/auth/admin.directory.group.member.readonly"
|
- "https://www.googleapis.com/auth/admin.directory.group.member.readonly"
|
||||||
|
- "https://www.googleapis.com/auth/admin.directory.customer.readonly"
|
||||||
extra-auth-params:
|
extra-auth-params:
|
||||||
access_type: "offline"
|
access_type: "offline"
|
||||||
prompt: "consent"
|
prompt: "consent"
|
||||||
|
- provider: "LINEAR"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-linear-client-id"
|
||||||
|
client-secret: "your-linear-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://linear.app/oauth/authorize"
|
||||||
|
token-url: "https://api.linear.app/oauth/token"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "read"
|
||||||
|
- "write"
|
||||||
|
- provider: "BREX"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-brex-client-id"
|
||||||
|
client-secret: "your-brex-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://accounts-api.brex.com/oauth2/default/v1/authorize"
|
||||||
|
token-url: "https://accounts-api.brex.com/oauth2/default/v1/token"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "openid"
|
||||||
|
- "offline_access"
|
||||||
|
- provider: "HUBSPOT"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-hubspot-client-id"
|
||||||
|
client-secret: "your-hubspot-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://app.hubspot.com/oauth/authorize"
|
||||||
|
token-url: "https://api.hubapi.com/oauth/v1/token"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "settings.users.read"
|
||||||
|
- provider: "DOCUSIGN"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-docusign-client-id"
|
||||||
|
client-secret: "your-docusign-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://account-d.docusign.com/oauth/auth"
|
||||||
|
token-url: "https://account-d.docusign.com/oauth/token"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "signature"
|
||||||
|
token-endpoint-auth: "basic-form"
|
||||||
|
- provider: "NOTION"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-notion-client-id"
|
||||||
|
client-secret: "your-notion-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://api.notion.com/v1/oauth/authorize"
|
||||||
|
token-url: "https://api.notion.com/v1/oauth/token"
|
||||||
|
|
||||||
|
extra-auth-params:
|
||||||
|
owner: "user"
|
||||||
|
token-endpoint-auth: "basic-json"
|
||||||
|
- provider: "GITHUB"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-github-client-id"
|
||||||
|
client-secret: "your-github-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://github.com/login/oauth/authorize"
|
||||||
|
token-url: "https://github.com/login/oauth/access_token"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "read:org"
|
||||||
|
- provider: "SENTRY"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-sentry-client-id"
|
||||||
|
client-secret: "your-sentry-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://sentry.io/oauth/authorize/"
|
||||||
|
token-url: "https://sentry.io/oauth/token/"
|
||||||
|
|
||||||
|
scopes:
|
||||||
|
- "org:read"
|
||||||
|
- "member:read"
|
||||||
|
- provider: "INTERCOM"
|
||||||
|
protocol: "oauth2"
|
||||||
|
config:
|
||||||
|
client-id: "your-intercom-client-id"
|
||||||
|
client-secret: "your-intercom-client-secret"
|
||||||
|
redirect-uri: "http://localhost:8080/api/console/v1/connectors/complete"
|
||||||
|
auth-url: "https://app.intercom.com/oauth"
|
||||||
|
token-url: "https://api.intercom.io/auth/eagle/token"
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -45,6 +45,7 @@ require (
|
|||||||
golang.org/x/oauth2 v0.35.0
|
golang.org/x/oauth2 v0.35.0
|
||||||
golang.org/x/sync v0.20.0
|
golang.org/x/sync v0.20.0
|
||||||
google.golang.org/api v0.269.0
|
google.golang.org/api v0.269.0
|
||||||
|
gopkg.in/dnaeon/go-vcr.v4 v4.0.6
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ require (
|
|||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
|
||||||
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect
|
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -366,6 +366,8 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
||||||
|
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||||
@@ -409,6 +411,8 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/dnaeon/go-vcr.v4 v4.0.6 h1:PiJkrakkmzc5s7EfBnZOnyiLwi7o7A9fwPzN0X2uwe0=
|
||||||
|
gopkg.in/dnaeon/go-vcr.v4 v4.0.6/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
67
pkg/coredata/access_entry_account_type_test.go
Normal file
67
pkg/coredata/access_entry_account_type_test.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessEntryAccountTypeScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessEntryAccountType
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "user string", input: "USER", want: AccessEntryAccountTypeUser},
|
||||||
|
{name: "service_account bytes", input: []byte("SERVICE_ACCOUNT"), want: AccessEntryAccountTypeServiceAccount},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessEntryAccountType
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessEntryAccountTypeValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := AccessEntryAccountTypeUser.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != "USER" {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, "USER")
|
||||||
|
}
|
||||||
|
}
|
||||||
86
pkg/coredata/access_entry_decision_test.go
Normal file
86
pkg/coredata/access_entry_decision_test.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessEntryDecisionScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessEntryDecision
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "pending string", input: "PENDING", want: AccessEntryDecisionPending},
|
||||||
|
{name: "approved string", input: "APPROVED", want: AccessEntryDecisionApproved},
|
||||||
|
{name: "revoke string", input: "REVOKE", want: AccessEntryDecisionRevoke},
|
||||||
|
{name: "defer bytes", input: []byte("DEFER"), want: AccessEntryDecisionDefer},
|
||||||
|
{name: "escalate string", input: "ESCALATE", want: AccessEntryDecisionEscalate},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessEntryDecision
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessEntryDecisionValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
decision AccessEntryDecision
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "pending", decision: AccessEntryDecisionPending, want: "PENDING"},
|
||||||
|
{name: "approved", decision: AccessEntryDecisionApproved, want: "APPROVED"},
|
||||||
|
{name: "revoke", decision: AccessEntryDecisionRevoke, want: "REVOKE"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := tt.decision.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
71
pkg/coredata/access_entry_flag_test.go
Normal file
71
pkg/coredata/access_entry_flag_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessEntryFlagScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessEntryFlag
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "none string", input: "NONE", want: AccessEntryFlagNone},
|
||||||
|
{name: "orphaned string", input: "ORPHANED", want: AccessEntryFlagOrphaned},
|
||||||
|
{name: "inactive string", input: "INACTIVE", want: AccessEntryFlagInactive},
|
||||||
|
{name: "excessive string", input: "EXCESSIVE", want: AccessEntryFlagExcessive},
|
||||||
|
{name: "role_mismatch bytes", input: []byte("ROLE_MISMATCH"), want: AccessEntryFlagRoleMismatch},
|
||||||
|
{name: "new string", input: "NEW", want: AccessEntryFlagNew},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessEntryFlag
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessEntryFlagValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := AccessEntryFlagNone.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != "NONE" {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, "NONE")
|
||||||
|
}
|
||||||
|
}
|
||||||
68
pkg/coredata/access_entry_incremental_tag_test.go
Normal file
68
pkg/coredata/access_entry_incremental_tag_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessEntryIncrementalTagScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessEntryIncrementalTag
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "new string", input: "NEW", want: AccessEntryIncrementalTagNew},
|
||||||
|
{name: "removed bytes", input: []byte("REMOVED"), want: AccessEntryIncrementalTagRemoved},
|
||||||
|
{name: "unchanged string", input: "UNCHANGED", want: AccessEntryIncrementalTagUnchanged},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessEntryIncrementalTag
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessEntryIncrementalTagValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := AccessEntryIncrementalTagNew.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != "NEW" {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, "NEW")
|
||||||
|
}
|
||||||
|
}
|
||||||
87
pkg/coredata/access_review_campaign_status_test.go
Normal file
87
pkg/coredata/access_review_campaign_status_test.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessReviewCampaignStatusScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessReviewCampaignStatus
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "draft string", input: "DRAFT", want: AccessReviewCampaignStatusDraft},
|
||||||
|
{name: "in_progress string", input: "IN_PROGRESS", want: AccessReviewCampaignStatusInProgress},
|
||||||
|
{name: "pending_actions string", input: "PENDING_ACTIONS", want: AccessReviewCampaignStatusPendingActions},
|
||||||
|
{name: "failed string", input: "FAILED", want: AccessReviewCampaignStatusFailed},
|
||||||
|
{name: "completed string", input: "COMPLETED", want: AccessReviewCampaignStatusCompleted},
|
||||||
|
{name: "cancelled bytes", input: []byte("CANCELLED"), want: AccessReviewCampaignStatusCancelled},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessReviewCampaignStatus
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessReviewCampaignStatusValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
status AccessReviewCampaignStatus
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "draft", status: AccessReviewCampaignStatusDraft, want: "DRAFT"},
|
||||||
|
{name: "in_progress", status: AccessReviewCampaignStatusInProgress, want: "IN_PROGRESS"},
|
||||||
|
{name: "completed", status: AccessReviewCampaignStatusCompleted, want: "COMPLETED"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := tt.status.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
69
pkg/coredata/access_source_category_test.go
Normal file
69
pkg/coredata/access_source_category_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2026 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 "testing"
|
||||||
|
|
||||||
|
func TestAccessSourceCategoryScan(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input any
|
||||||
|
want AccessSourceCategory
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "saas string", input: "SAAS", want: AccessSourceCategorySaaS},
|
||||||
|
{name: "cloud_infra string", input: "CLOUD_INFRA", want: AccessSourceCategoryCloudInfra},
|
||||||
|
{name: "source_code bytes", input: []byte("SOURCE_CODE"), want: AccessSourceCategorySourceCode},
|
||||||
|
{name: "other string", input: "OTHER", want: AccessSourceCategoryOther},
|
||||||
|
{name: "invalid value", input: "BOGUS", wantErr: true},
|
||||||
|
{name: "unsupported type", input: 42, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var got AccessSourceCategory
|
||||||
|
err := got.Scan(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Scan(%v) expected error", tt.input)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Scan(%v) returned error: %v", tt.input, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("Scan(%v) = %q, want %q", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessSourceCategoryValue(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got, err := AccessSourceCategorySaaS.Value()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Value() returned error: %v", err)
|
||||||
|
}
|
||||||
|
if got != "SAAS" {
|
||||||
|
t.Fatalf("Value() = %q, want %q", got, "SAAS")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,36 @@
|
|||||||
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
-- Rename priority to rank
|
||||||
--
|
ALTER TABLE tasks RENAME COLUMN priority TO rank;
|
||||||
-- 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.
|
|
||||||
|
|
||||||
-- Add organization_id to access_entries and access_entry_decision_history
|
ALTER TABLE tasks DROP CONSTRAINT tasks_organization_id_state_priority_key;
|
||||||
-- to avoid JOINs in AuthorizationAttributes lookups.
|
|
||||||
|
|
||||||
-- 1. access_entries
|
-- Add task priority enum
|
||||||
ALTER TABLE access_entries
|
CREATE TYPE task_priority AS ENUM ('URGENT', 'HIGH', 'MEDIUM', 'LOW');
|
||||||
ADD COLUMN organization_id TEXT REFERENCES organizations(id);
|
|
||||||
|
|
||||||
UPDATE access_entries ae
|
ALTER TABLE tasks ADD COLUMN priority task_priority NOT NULL DEFAULT 'MEDIUM'::task_priority;
|
||||||
SET organization_id = arc.organization_id
|
|
||||||
FROM access_review_campaigns arc
|
|
||||||
WHERE ae.access_review_campaign_id = arc.id;
|
|
||||||
|
|
||||||
ALTER TABLE access_entries
|
ALTER TABLE tasks ALTER COLUMN priority DROP DEFAULT;
|
||||||
ALTER COLUMN organization_id SET NOT NULL;
|
|
||||||
|
|
||||||
-- 2. access_entry_decision_history
|
-- Rank is now scoped to (state, priority) — backfill ranks per group
|
||||||
ALTER TABLE access_entry_decision_history
|
WITH ranked AS (
|
||||||
ADD COLUMN organization_id TEXT REFERENCES organizations(id);
|
SELECT id, ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY organization_id, state, priority
|
||||||
|
ORDER BY rank
|
||||||
|
) AS new_rank
|
||||||
|
FROM tasks
|
||||||
|
)
|
||||||
|
UPDATE tasks SET rank = ranked.new_rank FROM ranked WHERE tasks.id = ranked.id;
|
||||||
|
|
||||||
UPDATE access_entry_decision_history h
|
ALTER TABLE tasks
|
||||||
SET organization_id = ae.organization_id
|
ADD CONSTRAINT tasks_organization_id_state_priority_rank_key
|
||||||
FROM access_entries ae
|
UNIQUE (organization_id, state, priority, rank)
|
||||||
WHERE h.access_entry_id = ae.id;
|
DEFERRABLE INITIALLY DEFERRED;
|
||||||
|
|
||||||
ALTER TABLE access_entry_decision_history
|
-- Computed column for composite ordering (priority level then rank)
|
||||||
ALTER COLUMN organization_id SET NOT NULL;
|
ALTER TABLE tasks ADD COLUMN priority_rank int GENERATED ALWAYS AS (
|
||||||
|
(CASE priority
|
||||||
|
WHEN 'URGENT' THEN 1
|
||||||
|
WHEN 'HIGH' THEN 2
|
||||||
|
WHEN 'MEDIUM' THEN 3
|
||||||
|
WHEN 'LOW' THEN 4
|
||||||
|
END) * 1000000 + rank
|
||||||
|
) STORED;
|
||||||
|
|||||||
40
pkg/coredata/migrations/20260330T120042Z.sql
Normal file
40
pkg/coredata/migrations/20260330T120042Z.sql
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
-- Copyright (c) 2026 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.
|
||||||
|
|
||||||
|
-- Add organization_id to access_entries and access_entry_decision_history
|
||||||
|
-- to avoid JOINs in AuthorizationAttributes lookups.
|
||||||
|
|
||||||
|
-- 1. access_entries
|
||||||
|
ALTER TABLE access_entries
|
||||||
|
ADD COLUMN organization_id TEXT REFERENCES organizations(id);
|
||||||
|
|
||||||
|
UPDATE access_entries ae
|
||||||
|
SET organization_id = arc.organization_id
|
||||||
|
FROM access_review_campaigns arc
|
||||||
|
WHERE ae.access_review_campaign_id = arc.id;
|
||||||
|
|
||||||
|
ALTER TABLE access_entries
|
||||||
|
ALTER COLUMN organization_id SET NOT NULL;
|
||||||
|
|
||||||
|
-- 2. access_entry_decision_history
|
||||||
|
ALTER TABLE access_entry_decision_history
|
||||||
|
ADD COLUMN organization_id TEXT REFERENCES organizations(id);
|
||||||
|
|
||||||
|
UPDATE access_entry_decision_history h
|
||||||
|
SET organization_id = ae.organization_id
|
||||||
|
FROM access_entries ae
|
||||||
|
WHERE h.access_entry_id = ae.id;
|
||||||
|
|
||||||
|
ALTER TABLE access_entry_decision_history
|
||||||
|
ALTER COLUMN organization_id SET NOT NULL;
|
||||||
@@ -53,6 +53,12 @@ type (
|
|||||||
Provider coredata.ConnectorProvider
|
Provider coredata.ConnectorProvider
|
||||||
Protocol coredata.ConnectorProtocol
|
Protocol coredata.ConnectorProtocol
|
||||||
Connection connector.Connection
|
Connection connector.Connection
|
||||||
|
TallySettings *coredata.TallyConnectorSettings
|
||||||
|
OnePasswordSettings *coredata.OnePasswordConnectorSettings
|
||||||
|
SentrySettings *coredata.SentryConnectorSettings
|
||||||
|
SupabaseSettings *coredata.SupabaseConnectorSettings
|
||||||
|
GitHubSettings *coredata.GitHubConnectorSettings
|
||||||
|
OnePasswordUsersAPISettings *coredata.OnePasswordUsersAPISettings
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,6 +100,30 @@ func (s *ConnectorService) ListForOrganizationID(
|
|||||||
return page.NewPage(connectors, cursor), nil
|
return page.NewPage(connectors, cursor), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConnectorService) ListAllForOrganizationID(
|
||||||
|
ctx context.Context,
|
||||||
|
organizationID gid.GID,
|
||||||
|
) (coredata.Connectors, error) {
|
||||||
|
var connectors coredata.Connectors
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
return connectors.LoadAllByOrganizationIDWithoutDecryptedConnection(
|
||||||
|
ctx,
|
||||||
|
conn,
|
||||||
|
s.svc.scope,
|
||||||
|
organizationID,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot list all connectors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectors, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ConnectorService) GetByOrganizationIDAndProvider(
|
func (s *ConnectorService) GetByOrganizationIDAndProvider(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
@@ -127,6 +157,25 @@ func (s *ConnectorService) GetByOrganizationIDAndProvider(
|
|||||||
return connectors[0], nil
|
return connectors[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConnectorService) Get(
|
||||||
|
ctx context.Context,
|
||||||
|
connectorID gid.GID,
|
||||||
|
) (*coredata.Connector, error) {
|
||||||
|
connector := &coredata.Connector{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
return connector.LoadMetadataByID(ctx, conn, s.svc.scope, connectorID)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get connector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return connector, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ConnectorService) Delete(
|
func (s *ConnectorService) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
connectorID gid.GID,
|
connectorID gid.GID,
|
||||||
@@ -161,6 +210,33 @@ func (s *ConnectorService) Create(
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case req.TallySettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.TallySettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set tally settings: %w", err)
|
||||||
|
}
|
||||||
|
case req.OnePasswordSettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.OnePasswordSettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set one password settings: %w", err)
|
||||||
|
}
|
||||||
|
case req.SentrySettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.SentrySettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set sentry settings: %w", err)
|
||||||
|
}
|
||||||
|
case req.SupabaseSettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.SupabaseSettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set supabase settings: %w", err)
|
||||||
|
}
|
||||||
|
case req.GitHubSettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.GitHubSettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set github settings: %w", err)
|
||||||
|
}
|
||||||
|
case req.OnePasswordUsersAPISettings != nil:
|
||||||
|
if err := newConnector.SetSettings(req.OnePasswordUsersAPISettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot set one password users api settings: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := s.svc.pg.WithConn(
|
err := s.svc.pg.WithConn(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
@@ -211,3 +287,30 @@ func (s *ConnectorService) Create(
|
|||||||
|
|
||||||
return newConnector, nil
|
return newConnector, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reconnect updates an existing connector's connection (token) without
|
||||||
|
// changing its settings or identity. Used when an OAuth token expires
|
||||||
|
// and the user re-authenticates.
|
||||||
|
func (s *ConnectorService) Reconnect(
|
||||||
|
ctx context.Context,
|
||||||
|
connectorID gid.GID,
|
||||||
|
connection connector.Connection,
|
||||||
|
) (*coredata.Connector, error) {
|
||||||
|
cnnctr := &coredata.Connector{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||||
|
if err := cnnctr.LoadMetadataByID(ctx, conn, s.svc.scope, connectorID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load connector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnnctr.Connection = connection
|
||||||
|
cnnctr.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
return cnnctr.Update(ctx, conn, s.svc.scope, s.svc.encryptionKey)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot reconnect connector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnnctr, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -410,6 +410,10 @@ enum ConnectorProvider
|
|||||||
SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack")
|
SLACK @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderSlack")
|
||||||
GOOGLE_WORKSPACE
|
GOOGLE_WORKSPACE
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderGoogleWorkspace")
|
||||||
|
BREX @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderBrex")
|
||||||
|
TALLY @goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderTally")
|
||||||
|
CLOUDFLARE
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ConnectorProviderCloudflare")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SCIMBridgeType
|
enum SCIMBridgeType
|
||||||
|
|||||||
Reference in New Issue
Block a user