Skip to content

Commit

Permalink
fix: overhaul firebase service to authenticate properly
Browse files Browse the repository at this point in the history
  • Loading branch information
ekristen committed May 21, 2024
1 parent 8546af2 commit af02c8b
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 21 deletions.
114 changes: 96 additions & 18 deletions pkg/gcputil/firebase.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package gcputil

import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"google.golang.org/api/option"
"google.golang.org/api/option/internaloption"
"io"
"io/ioutil"

Check failure on line 12 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details. (staticcheck)
"net/http"
"time"

"golang.org/x/oauth2/google"
htransport "google.golang.org/api/transport/http"
)

// FirebaseDBClient is a client to interact with the Firebase Realtime Database API
Expand All @@ -25,26 +30,99 @@ type DatabaseInstance struct {
State string `json:"state"`
}

// NewFirebaseDBClient creates a new Firebase Realtime Database client to interact with the
type loggingTransport struct {

Check failure on line 33 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

type `loggingTransport` is unused (unused)
transport http.RoundTripper
}

func (t *loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {

Check failure on line 37 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `(*loggingTransport).RoundTrip` is unused (unused)
start := time.Now()

// Log the request
logRequest(req)

// Perform the request
res, err := t.transport.RoundTrip(req)
if err != nil {
fmt.Printf("Error: %v\n", err)
return nil, err
}

// Log the response
logResponse(res, time.Since(start))

return res, nil
}

func logRequest(req *http.Request) {

Check failure on line 56 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `logRequest` is unused (unused)
fmt.Printf("Request: %s %s\n", req.Method, req.URL)
fmt.Printf("Headers: %v\n", req.Header)

if req.Body != nil {
bodyBytes, err := ioutil.ReadAll(req.Body)
if err != nil {
fmt.Printf("Error reading request body: %v\n", err)
} else {
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Reassign the body
fmt.Printf("Body: %s\n", string(bodyBytes))
}
}
}

func logResponse(res *http.Response, duration time.Duration) {

Check failure on line 71 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `logResponse` is unused (unused)
fmt.Printf("Response: %s in %v\n", res.Status, duration)
fmt.Printf("Headers: %v\n", res.Header)

if res.Body != nil {
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Printf("Error reading response body: %v\n", err)
} else {
res.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Reassign the body
fmt.Printf("Body: %s\n", string(bodyBytes))
}
}
}

const basePath = "https://firebasedatabase.googleapis.com/"
const basePathTemplate = "https://firebasedatabase.UNIVERSE_DOMAIN/"
const mtlsBasePath = "https://firebasedatabase.mtls.googleapis.com/"

// NewFirebaseDatabaseService creates a new Firebase Realtime Database client to interact with the
// https://firebasedatabase.googleapis.com endpoints as there is no official golang client library
func NewFirebaseDBClient(ctx context.Context) (*FirebaseDBClient, error) {
client, err := google.DefaultClient(ctx,
func NewFirebaseDatabaseService(ctx context.Context, opts ...option.ClientOption) (*FirebaseDatabaseService, error) {
scopesOption := internaloption.WithDefaultScopes(
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/cloud-platform.read-only",
"https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/firebase.readonly",
)
// NOTE: prepend, so we don't override user-specified scopes.
opts = append([]option.ClientOption{scopesOption}, opts...)
opts = append(opts, internaloption.WithDefaultEndpoint(basePath))

Check failure on line 101 in pkg/gcputil/firebase.go

View workflow job for this annotation

GitHub Actions / golangci-lint

SA1019: internaloption.WithDefaultEndpoint is deprecated: WithDefaultEndpoint does not support setting the universe domain. Use WithDefaultEndpointTemplate and WithDefaultUniverseDomain to compose the default endpoint instead. (staticcheck)
opts = append(opts, internaloption.WithDefaultEndpointTemplate(basePathTemplate))
opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
opts = append(opts, internaloption.EnableNewAuthLibrary())
client, endpoint, err := htransport.NewClient(ctx, opts...)
if err != nil {
return nil, fmt.Errorf("failed to parse JWT from credentials: %v", err)
return nil, err
}

s := &FirebaseDatabaseService{client: client, BasePath: basePath}
if endpoint != "" {
s.BasePath = endpoint
}

return &FirebaseDBClient{
httpClient: client,
}, nil
return s, nil
}

type FirebaseDatabaseService struct {
client *http.Client
BasePath string // API endpoint base URL
UserAgent string // optional additional User-Agent fragment
}

// ListDatabaseRegions lists Firebase Realtime Database regions
func (c *FirebaseDBClient) ListDatabaseRegions() []string {
func (s *FirebaseDatabaseService) ListDatabaseRegions() []string {
return []string{
"us-central1",
"europe-west1",
Expand All @@ -53,16 +131,16 @@ func (c *FirebaseDBClient) ListDatabaseRegions() []string {
}

// ListDatabaseInstances lists Firebase Realtime Database instances
func (c *FirebaseDBClient) ListDatabaseInstances(ctx context.Context, parent string) ([]*DatabaseInstance, error) {
url1 := fmt.Sprintf("https://firebasedatabase.googleapis.com/v1beta/%s/instances", parent)
func (s *FirebaseDatabaseService) ListDatabaseInstances(ctx context.Context, parent string) ([]*DatabaseInstance, error) {
url1 := fmt.Sprintf("%sv1beta/%s/instances", s.BasePath, parent)
logrus.Tracef("url: %s", url1)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, url1, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %v", err)
}

resp, err := c.httpClient.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("error requesting database instances: %v", err)
}
Expand All @@ -84,16 +162,16 @@ func (c *FirebaseDBClient) ListDatabaseInstances(ctx context.Context, parent str
}

// DeleteDatabaseInstance deletes a Firebase Realtime Database instance
func (c *FirebaseDBClient) DeleteDatabaseInstance(ctx context.Context, parent, name string) error {
url := fmt.Sprintf("https://firebasedatabase.googleapis.com/v1beta/%s/instances/%s", parent, name)
func (s *FirebaseDatabaseService) DeleteDatabaseInstance(ctx context.Context, parent, name string) error {
url := fmt.Sprintf("%sv1beta/%s/instances/%s", s.BasePath, parent, name)
logrus.Tracef("url: %s", url)

req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}

resp, err := c.httpClient.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("error deleting database instance: %v", err)
}
Expand All @@ -107,16 +185,16 @@ func (c *FirebaseDBClient) DeleteDatabaseInstance(ctx context.Context, parent, n
}

// DisableDatabaseInstance disables a Firebase Realtime Database instance
func (c *FirebaseDBClient) DisableDatabaseInstance(ctx context.Context, parent, name string) error {
url := fmt.Sprintf("https://firebasedatabase.googleapis.com/v1beta/%s/instances/%s:disable", parent, name)
func (s *FirebaseDatabaseService) DisableDatabaseInstance(ctx context.Context, parent, name string) error {
url := fmt.Sprintf("%sv1beta/%s/instances/%s:disable", s.BasePath, parent, name)
logrus.Tracef("url: %s", url)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}

resp, err := c.httpClient.Do(req)
resp, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("error disabling database instance: %v", err)
}
Expand Down
11 changes: 8 additions & 3 deletions resources/firebase-realtime-database.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"fmt"
"slices"
"strings"
"time"

"github.com/gotidy/ptr"

firebase "firebase.google.com/go"

liberror "github.com/ekristen/libnuke/pkg/errors"
"github.com/ekristen/libnuke/pkg/registry"
"github.com/ekristen/libnuke/pkg/resource"
Expand All @@ -33,7 +35,7 @@ func init() {
}

type FirebaseRealtimeDatabaseLister struct {
svc *gcputil.FirebaseDBClient
svc *gcputil.FirebaseDatabaseService
}

func (l *FirebaseRealtimeDatabaseLister) List(ctx context.Context, o interface{}) ([]resource.Resource, error) {
Expand All @@ -46,7 +48,7 @@ func (l *FirebaseRealtimeDatabaseLister) List(ctx context.Context, o interface{}

if l.svc == nil {
var err error
l.svc, err = gcputil.NewFirebaseDBClient(ctx)
l.svc, err = gcputil.NewFirebaseDatabaseService(ctx)
if err != nil {
return nil, err
}
Expand All @@ -57,6 +59,9 @@ func (l *FirebaseRealtimeDatabaseLister) List(ctx context.Context, o interface{}
return nil, liberror.ErrSkipRequest("region is not supported")
}

ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()

resp, err := l.svc.ListDatabaseInstances(ctx, fmt.Sprintf("projects/%s/locations/%s", *opts.Project, *opts.Region))
if err != nil {
return nil, err
Expand Down Expand Up @@ -87,7 +92,7 @@ func (l *FirebaseRealtimeDatabaseLister) List(ctx context.Context, o interface{}
}

type FirebaseRealtimeDatabase struct {
svc *gcputil.FirebaseDBClient
svc *gcputil.FirebaseDatabaseService
settings *settings.Setting
Project *string
Region *string
Expand Down

0 comments on commit af02c8b

Please sign in to comment.