Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
222 changes: 222 additions & 0 deletions cmd/android-amapi-mock/google_forwarder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"

"google.golang.org/api/androidmanagement/v1"
"google.golang.org/api/option"
)

// googleForwarder wraps an authenticated Google Android Management API client
// for forwarding requests targeting real devices.
type googleForwarder struct {
svc *androidmanagement.Service
}

func newGoogleForwarder(credentialsFile string) (*googleForwarder, error) {
credJSON, err := os.ReadFile(credentialsFile)
if err != nil {
return nil, fmt.Errorf("read credentials file: %w", err)
}

ctx := context.Background()
svc, err := androidmanagement.NewService(ctx,
option.WithCredentialsJSON(credJSON),

Check failure on line 30 in cmd/android-amapi-mock/google_forwarder.go

View workflow job for this annotation

GitHub Actions / lint-incremental (ubuntu-4core)

SA1019: option.WithCredentialsJSON is deprecated: This function is being deprecated because of a potential security risk. (staticcheck)
)
if err != nil {
return nil, fmt.Errorf("create android management service: %w", err)
}

return &googleForwarder{svc: svc}, nil
}

// ForwardDevicesGet forwards a GET .../devices/{id} request to Google.
func (g *googleForwarder) ForwardDevicesGet(w http.ResponseWriter, r *http.Request) {
name := deviceName(r)
device, err := g.svc.Enterprises.Devices.Get(name).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, device)
}

// ForwardDevicesPatch forwards a PATCH .../devices/{id} request to Google.
func (g *googleForwarder) ForwardDevicesPatch(w http.ResponseWriter, r *http.Request) {
name := deviceName(r)
var device androidmanagement.Device
if err := readBody(r, &device); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
result, err := g.svc.Enterprises.Devices.Patch(name, &device).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, result)
}

// ForwardDevicesDelete forwards a DELETE .../devices/{id} request to Google.
func (g *googleForwarder) ForwardDevicesDelete(w http.ResponseWriter, r *http.Request) {
name := deviceName(r)
_, err := g.svc.Enterprises.Devices.Delete(name).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, "{}")
}

// ForwardIssueCommand forwards a POST .../devices/{id}:issueCommand request to Google.
func (g *googleForwarder) ForwardIssueCommand(w http.ResponseWriter, r *http.Request) {
name := deviceName(r)
var cmd androidmanagement.Command
if err := readBody(r, &cmd); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
op, err := g.svc.Enterprises.Devices.IssueCommand(name, &cmd).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, op)
}

// ForwardDevicesList forwards a GET .../devices request to Google and returns all device names.
func (g *googleForwarder) ForwardDevicesList(enterpriseName string, ctx context.Context) []map[string]string {
var allDevices []map[string]string
pageToken := ""

for {
call := g.svc.Enterprises.Devices.List(enterpriseName).Context(ctx).PageSize(100).Fields("nextPageToken", "devices/name")
if pageToken != "" {
call = call.PageToken(pageToken)
}
resp, err := call.Do()
if err != nil {
log.Printf("googleForwarder: list devices failed: %v", err)
break
}
Comment thread
ksykulev marked this conversation as resolved.
for _, d := range resp.Devices {
allDevices = append(allDevices, map[string]string{"name": d.Name})
}
if resp.NextPageToken == "" {
break
}
pageToken = resp.NextPageToken
}

return allDevices
}

// ForwardPoliciesPatch forwards a PATCH .../policies/{id} request to Google.
func (g *googleForwarder) ForwardPoliciesPatch(w http.ResponseWriter, r *http.Request) {
name := policyName(r)
var policy androidmanagement.Policy
if err := readBody(r, &policy); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
result, err := g.svc.Enterprises.Policies.Patch(name, &policy).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, result)
}

// ForwardEnrollmentTokenCreate forwards a POST .../enrollmentTokens request to Google.
func (g *googleForwarder) ForwardEnrollmentTokenCreate(w http.ResponseWriter, r *http.Request) {
enterpriseName := "enterprises/" + r.PathValue("eid")
var token androidmanagement.EnrollmentToken
if err := readBody(r, &token); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
result, err := g.svc.Enterprises.EnrollmentTokens.Create(enterpriseName, &token).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, result)
}

// ForwardApplicationsGet forwards a GET .../applications/{package} request to Google.
func (g *googleForwarder) ForwardApplicationsGet(w http.ResponseWriter, r *http.Request) {
name := "enterprises/" + r.PathValue("eid") + "/applications/" + r.PathValue("pkg")
result, err := g.svc.Enterprises.Applications.Get(name).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, result)
}

// ForwardWebAppsCreate forwards a POST .../webApps request to Google.
func (g *googleForwarder) ForwardWebAppsCreate(w http.ResponseWriter, r *http.Request) {
enterpriseName := "enterprises/" + r.PathValue("eid")
var webApp androidmanagement.WebApp
if err := readBody(r, &webApp); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
result, err := g.svc.Enterprises.WebApps.Create(enterpriseName, &webApp).Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, result)
}

// ForwardEnterprisesList forwards a GET /v1/enterprises request to Google.
func (g *googleForwarder) ForwardEnterprisesList(w http.ResponseWriter, r *http.Request) {
resp, err := g.svc.Enterprises.List().Context(r.Context()).Do()
if err != nil {
writeGoogleError(w, err)
return
}
writeJSON(w, resp)
}

// ---- helpers ----

func readBody(r *http.Request, v any) error {
if r.Body == nil {
return nil
}
body, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
if len(body) == 0 {
return nil
}
return json.Unmarshal(body, v)
}

func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(v) //nolint:errcheck
}

func writeGoogleError(w http.ResponseWriter, err error) {
log.Printf("googleForwarder: Google API error: %v", err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadGateway)
json.NewEncoder(w).Encode(map[string]any{

Check failure on line 215 in cmd/android-amapi-mock/google_forwarder.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-4core)

Error return value of `(*encoding/json.Encoder).Encode` is not checked (errcheck)
"error": map[string]any{
"code": 502,
"message": err.Error(),
"status": "BAD_GATEWAY",
},
}) //nolint:errcheck
}
Loading
Loading