diff --git a/pkg/app/api/httpapi/callback.go b/pkg/app/api/httpapi/callback.go
index 1343c4bb58..a84ae4843a 100644
--- a/pkg/app/api/httpapi/callback.go
+++ b/pkg/app/api/httpapi/callback.go
@@ -77,7 +77,7 @@ func (h *authHandler) handleCallback(w http.ResponseWriter, r *http.Request) {
}
}
- user, err := getUser(ctx, sso, proj.Rbac, proj.Id, authCode)
+ user, err := getUser(ctx, sso, proj.Rbac, proj, authCode)
if err != nil {
h.handleError(w, r, "Unable to find user", err)
return
@@ -131,13 +131,13 @@ func checkState(r *http.Request, key string) error {
return nil
}
-func getUser(ctx context.Context, sso *model.ProjectSSOConfig, rbac *model.ProjectRBACConfig, projectID, code string) (*model.User, error) {
+func getUser(ctx context.Context, sso *model.ProjectSSOConfig, rbac *model.ProjectRBACConfig, project *model.Project, code string) (*model.User, error) {
switch sso.Provider {
case model.ProjectSSOConfig_GITHUB, model.ProjectSSOConfig_GITHUB_ENTERPRISE:
if sso.Github == nil {
return nil, fmt.Errorf("missing GitHub oauth in the SSO configuration")
}
- cli, err := github.NewOAuthClient(ctx, sso.Github, rbac, projectID, code)
+ cli, err := github.NewOAuthClient(ctx, sso.Github, rbac, project, code)
if err != nil {
return nil, err
}
diff --git a/pkg/app/ops/handler/handler.go b/pkg/app/ops/handler/handler.go
index 49c9b35714..20d6575320 100644
--- a/pkg/app/ops/handler/handler.go
+++ b/pkg/app/ops/handler/handler.go
@@ -164,9 +164,10 @@ func (h *Handler) handleAddProject(w http.ResponseWriter, r *http.Request) {
}
var (
- id = r.FormValue("ID")
- description = r.FormValue("Description")
- sharedSSOName = r.FormValue("SharedSSO")
+ id = r.FormValue("ID")
+ description = r.FormValue("Description")
+ sharedSSOName = r.FormValue("SharedSSO")
+ allowStrayAsViewer = r.FormValue("AllowStrayAsViewer") == "on"
)
if id == "" {
http.Error(w, "invalid id", http.StatusBadRequest)
@@ -188,9 +189,10 @@ func (h *Handler) handleAddProject(w http.ResponseWriter, r *http.Request) {
var (
project = &model.Project{
- Id: id,
- Desc: description,
- SharedSsoName: sharedSSOName,
+ Id: id,
+ Desc: description,
+ SharedSsoName: sharedSSOName,
+ AllowStrayAsViewer: allowStrayAsViewer,
}
username = model.GenerateRandomString(10)
password = model.GenerateRandomString(30)
diff --git a/pkg/app/ops/handler/templates/AddProject b/pkg/app/ops/handler/templates/AddProject
index 85f18f4793..0882297cc0 100644
--- a/pkg/app/ops/handler/templates/AddProject
+++ b/pkg/app/ops/handler/templates/AddProject
@@ -22,6 +22,8 @@ label {
+
+
diff --git a/pkg/app/web/src/__fixtures__/dummy-project.ts b/pkg/app/web/src/__fixtures__/dummy-project.ts
index c710d83195..64e9d5bcfa 100644
--- a/pkg/app/web/src/__fixtures__/dummy-project.ts
+++ b/pkg/app/web/src/__fixtures__/dummy-project.ts
@@ -19,6 +19,7 @@ export const dummyProject: Project.AsObject = {
createdAt: createdAt.unix(),
updatedAt: updatedAt.unix(),
staticAdminDisabled: false,
+ allowStrayAsViewer: false,
rbac: {
admin: "admin-team",
editor: "editor-team",
@@ -38,6 +39,7 @@ export function createProjectFromObject(o: Project.AsObject): Project {
project.setCreatedAt(o.createdAt);
project.setUpdatedAt(o.updatedAt);
project.setStaticAdminDisabled(o.staticAdminDisabled);
+ project.setAllowStrayAsViewer(o.allowStrayAsViewer);
if (o.rbac) {
const rbac = new ProjectRBACConfig();
rbac.setAdmin(o.rbac.admin);
diff --git a/pkg/model/project.proto b/pkg/model/project.proto
index a63ce9fedf..9fba20668c 100644
--- a/pkg/model/project.proto
+++ b/pkg/model/project.proto
@@ -42,6 +42,10 @@ message Project {
// It will be enabled when this parameter has no empty value.
string shared_sso_name = 7;
+ // Enable this field will allow users not belonging
+ // to any registered teams to log in with Viewer role.
+ bool allow_stray_as_viewer = 8;
+
// Unix time when the project is created.
int64 created_at = 14 [(validate.rules).int64.gt = 0];
// Unix time of the last time when the project is updated.
diff --git a/pkg/oauth/github/github.go b/pkg/oauth/github/github.go
index 1827380723..841ed73d11 100644
--- a/pkg/oauth/github/github.go
+++ b/pkg/oauth/github/github.go
@@ -31,7 +31,8 @@ import (
type OAuthClient struct {
*github.Client
- projectID string
+ project *model.Project
+
adminTeam string
editorTeam string
viewerTeam string
@@ -41,10 +42,11 @@ type OAuthClient struct {
func NewOAuthClient(ctx context.Context,
sso *model.ProjectSSOConfig_GitHub,
rbac *model.ProjectRBACConfig,
- projectID, code string,
+ project *model.Project,
+ code string,
) (*OAuthClient, error) {
c := &OAuthClient{
- projectID: projectID,
+ project: project,
adminTeam: rbac.Admin,
editorTeam: rbac.Editor,
viewerTeam: rbac.Viewer,
@@ -115,7 +117,7 @@ func (c *OAuthClient) GetUser(ctx context.Context) (*model.User, error) {
Username: user.GetLogin(),
AvatarUrl: user.GetAvatarURL(),
Role: &model.Role{
- ProjectId: c.projectID,
+ ProjectId: c.project.Id,
ProjectRole: role,
},
}, nil
@@ -151,6 +153,14 @@ func (c *OAuthClient) decideRole(user string, teams []*github.Team) (role model.
return
}
+ // In case the current user does not belong to any registered
+ // teams, if AllowStrayAsViewer option is set, assign Viewer role
+ // as user's role.
+ if c.project.AllowStrayAsViewer {
+ role = model.Role_VIEWER
+ return
+ }
+
err = fmt.Errorf("user (%s) not found in any of the %d project teams", user, len(teams))
return
}
diff --git a/pkg/oauth/github/github_test.go b/pkg/oauth/github/github_test.go
index 915befc1d6..864155d461 100644
--- a/pkg/oauth/github/github_test.go
+++ b/pkg/oauth/github/github_test.go
@@ -29,6 +29,7 @@ func TestDecideRole(t *testing.T) {
cases := []struct {
name string
username string
+ oc *OAuthClient
teams []*github.Team
role model.Role_ProjectRole
wantErr bool
@@ -36,6 +37,14 @@ func TestDecideRole(t *testing.T) {
{
name: "nothing",
username: "foo",
+ oc: &OAuthClient{
+ adminTeam: "org/team-admin",
+ editorTeam: "org/team-editor",
+ viewerTeam: "org/team-viewer",
+ project: &model.Project{
+ AllowStrayAsViewer: false,
+ },
+ },
teams: []*github.Team{
{
Organization: &github.Organization{Login: stringPointer("org")},
@@ -44,9 +53,34 @@ func TestDecideRole(t *testing.T) {
},
wantErr: true,
},
+ {
+ name: "viewer as default",
+ username: "foo",
+ oc: &OAuthClient{
+ adminTeam: "org/team-admin",
+ editorTeam: "org/team-editor",
+ viewerTeam: "org/team-viewer",
+ project: &model.Project{
+ AllowStrayAsViewer: true,
+ },
+ },
+ teams: []*github.Team{
+ {
+ Organization: &github.Organization{Login: stringPointer("org")},
+ Slug: stringPointer("team1"),
+ },
+ },
+ role: model.Role_VIEWER,
+ wantErr: false,
+ },
{
name: "admin",
username: "foo",
+ oc: &OAuthClient{
+ adminTeam: "org/team-admin",
+ editorTeam: "org/team-editor",
+ viewerTeam: "org/team-viewer",
+ },
teams: []*github.Team{
{
Organization: &github.Organization{Login: stringPointer("org")},
@@ -66,6 +100,11 @@ func TestDecideRole(t *testing.T) {
{
name: "editor",
username: "foo",
+ oc: &OAuthClient{
+ adminTeam: "org/team-admin",
+ editorTeam: "org/team-editor",
+ viewerTeam: "org/team-viewer",
+ },
teams: []*github.Team{
{
Organization: &github.Organization{Login: stringPointer("org")},
@@ -85,6 +124,11 @@ func TestDecideRole(t *testing.T) {
{
name: "viewer",
username: "foo",
+ oc: &OAuthClient{
+ adminTeam: "org/team-admin",
+ editorTeam: "org/team-editor",
+ viewerTeam: "org/team-viewer",
+ },
teams: []*github.Team{
{
Organization: &github.Organization{Login: stringPointer("org")},
@@ -103,14 +147,9 @@ func TestDecideRole(t *testing.T) {
},
}
- oc := &OAuthClient{
- adminTeam: "org/team-admin",
- editorTeam: "org/team-editor",
- viewerTeam: "org/team-viewer",
- }
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
- role, err := oc.decideRole(tc.username, tc.teams)
+ role, err := tc.oc.decideRole(tc.username, tc.teams)
assert.Equal(t, tc.wantErr, err != nil)
if err == nil {
assert.Equal(t, tc.role, role)