226 lines
6.7 KiB
Go
226 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
|
|
"golang.org/x/oauth2/google"
|
|
"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(credentialsJSON string) (*googleForwarder, error) {
|
|
ctx := context.Background()
|
|
creds, err := google.CredentialsFromJSON(ctx, []byte(credentialsJSON), androidmanagement.AndroidmanagementScope) //nolint:staticcheck // SA1019 -- load testing tool, credentials are from a trusted source
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse credentials: %w", err)
|
|
}
|
|
|
|
svc, err := androidmanagement.NewService(ctx,
|
|
option.WithCredentials(creds),
|
|
)
|
|
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, error) {
|
|
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 {
|
|
return nil, fmt.Errorf("list devices from Google: %w", err)
|
|
}
|
|
for _, d := range resp.Devices {
|
|
allDevices = append(allDevices, map[string]string{"name": d.Name})
|
|
}
|
|
if resp.NextPageToken == "" {
|
|
break
|
|
}
|
|
pageToken = resp.NextPageToken
|
|
}
|
|
|
|
return allDevices, nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
call := g.svc.Enterprises.Policies.Patch(name, &policy).Context(r.Context())
|
|
if mask := r.URL.Query().Get("updateMask"); mask != "" {
|
|
call = call.UpdateMask(mask)
|
|
}
|
|
result, err := call.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{
|
|
"error": map[string]any{
|
|
"code": 502,
|
|
"message": err.Error(),
|
|
"status": "BAD_GATEWAY",
|
|
},
|
|
}) //nolint:errcheck
|
|
}
|