Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Write Permission Checking to use an Internal Database Table instead of Firebase Custom Claims. #16

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/schema/email_write_access.schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE email_write_access (
email varchar(1024) NOT NULL,
orgName text NOT NULL,
PRIMARY KEY(email, orgname)
);
9 changes: 4 additions & 5 deletions graph/schema.resolvers.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (

"github.com/openconfig/catalog-server/graph/generated"
"github.com/openconfig/catalog-server/graph/model"
"github.com/openconfig/catalog-server/pkg/access"
"github.com/openconfig/catalog-server/pkg/db"
"github.com/openconfig/catalog-server/pkg/dbtograph"
"github.com/openconfig/catalog-server/pkg/validate"
Expand All @@ -34,7 +33,7 @@ func (r *mutationResolver) CreateModule(ctx context.Context, input model.NewModu
successMsg := `Success`

// Validate the token and check whether it contains access to certain organization
if err := access.CheckAccess(token, input.OrgName); err != nil {
if err := db.CheckAccess(token, input.OrgName); err != nil {
return failMsg, fmt.Errorf("CreateModule: validate token failed: %v", err)
}

Expand All @@ -57,7 +56,7 @@ func (r *mutationResolver) DeleteModule(ctx context.Context, input model.ModuleK
successMsg := `Success`

// Validate the token and check whether it contains access to certain organization
if err := access.CheckAccess(token, input.OrgName); err != nil {
if err := db.CheckAccess(token, input.OrgName); err != nil {
return failMsg, fmt.Errorf("DeleteModule: validate token failed: %v", err)
}

Expand All @@ -74,7 +73,7 @@ func (r *mutationResolver) CreateFeatureBundle(ctx context.Context, input model.
successMsg := `Success`

// Validate the token and check whether it contains access to certain organization
if err := access.CheckAccess(token, input.OrgName); err != nil {
if err := db.CheckAccess(token, input.OrgName); err != nil {
return failMsg, fmt.Errorf("CreateFeatureBundle: validate token failed: %v", err)
}

Expand All @@ -97,7 +96,7 @@ func (r *mutationResolver) DeleteFeatureBundle(ctx context.Context, input model.
successMsg := `Success`

// Validate the token and check whether it contains access to certain organization
if err := access.CheckAccess(token, input.OrgName); err != nil {
if err := db.CheckAccess(token, input.OrgName); err != nil {
return failMsg, fmt.Errorf("DeleteFeatureBundle: validate token failed: %v", err)
}

Expand Down
116 changes: 0 additions & 116 deletions pkg/access/access.go

This file was deleted.

102 changes: 102 additions & 0 deletions pkg/db/access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package db

import (
"context"
"database/sql"
"fmt"
"os"

firebase "firebase.google.com/go/v4"

// Go postgres driver for Go's database/sql package
_ "github.com/lib/pq"
)

const (
// getEmailWriteAccess returns a list of orgnames to which the given email has write access.
getEmailWriteAccess = `SELECT orgname FROM email_write_access WHERE email = $1`
)

// readEmailOrgAccesses scans from a single-attribute relation of orgName strings.
func readEmailOrgAccesses(rows *sql.Rows) (map[string]bool, error) {
orgNames := map[string]bool{}
for rows.Next() {
var orgName string
if err := rows.Scan(&orgName); err != nil {
return nil, fmt.Errorf("readEmailAccesses db scan: %v", err)
}
orgNames[orgName] = true
}
return orgNames, nil
}

// getEmailOrgAccesses gets the set of orgNames to which a particular email has
// write access.
func getEmailOrgAccesses(email string) (map[string]bool, error) {
rows, err := db.Query(getEmailWriteAccess, email)
if err != nil {
return nil, err
}
defer rows.Close()

return readEmailOrgAccesses(rows)
}

// CheckAccess takes input of a string of token and a string of organization's name.
// It checks whether the given token in valid and whether the associated user
// has write access for the given organization name.
// If not, an error is returned.
func CheckAccess(token string, orgName string) error {
// Set up firebase configuration to use correct token validation method.
ctx := context.Background()
projectID, ok := os.LookupEnv("PROJECT_ID")
if !ok {
return fmt.Errorf("$PROJECT_ID not set")
}
config := &firebase.Config{ProjectID: projectID}
app, err := firebase.NewApp(ctx, config)
if err != nil {
return fmt.Errorf("access: error initializing app: %v\n", err)
}
client, err := app.Auth(ctx)
if err != nil {
return fmt.Errorf("access: generate firebase authentication admin failed")
}

// Use firebase to validate token
verifiedToken, err := client.VerifyIDToken(ctx, token)
if err != nil {
return fmt.Errorf("error verifying ID token: %v\n", err)
}

userRecord, err := client.GetUser(ctx, verifiedToken.UID)
if err != nil {
return fmt.Errorf("access: GetUser failed: %v", err)
}

accesses, err := getEmailOrgAccesses(userRecord.Email)
if err != nil {
return fmt.Errorf("access: getEmailOrgAccesses failed: %v", err)
}

if !accesses[orgName] {
// If the token does not contain access to input.OrgName, return an error.
return fmt.Errorf("user does not have access to organization %s", orgName)
}

return nil
}
29 changes: 20 additions & 9 deletions scripts/admin/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
### Overview
This directory contains scripts for helping admin of catalog system. It provides functionality to:
+ Delete existing account.
+ Grant write access of organizations to an existing account.

It does not provide functionalities for admin to register a new user as it can register
via the login page by itself.
This directory contains scripts for administering the catalog system. It
currently provides the following functionalities:

+ Delete existing accounts. can alternatively be done in the Firebase UI. This
may be deprecated in the future since it requires service account
impersonation.

It does not provide functionalities for registering a new user as users can
register via the login page directly.

It also does not provide the functionality to grant write access to certain
organizations for an existing account. This must be done by directly inserting
and deleting entries within an administrative SQL table. Although Firebase
custom claims can also achieve this purpose, they require service accounts and
generating a private key for management, which may present more of a security
risk, and is more cumbersome than just running some SQL statements.

### Usage
+ To use these scripts the user must be admin of identity platform where the catalog system is deployed.
+ To `delete`, run `go run deleteaccount.go -email EMAIL-OF-ACCOUNT`.
+ To `grant access`, run `go run grantaccess.go -db NAME-OF-DB -email EMAIL-OF-ACCOUNT -access STRING-OF_CLAIMS`. `STRING-OF_CLAIMS` is a string of list of organizations seperated by comma. That is, we expect the name of organization do not contain comma. Or we can choose a different delimiter with no conflicts with names of organizations. If user does not provide `-access STRING-OF_CLAIMS` is equivalent to setting access of this account to `empty access` (i.e., no write access to any organization).
+ To `read all existing users' access`, run `go run grantacces.go -db NAME-OF-DB -all`.

+ To use these scripts the user must be admin of identity platform where the
catalog system is deployed.
+ To `delete`, run `go run deleteaccount.go -email EMAIL-OF-ACCOUNT`.
30 changes: 0 additions & 30 deletions scripts/admin/create-ca.sh

This file was deleted.

Loading