Adding a VPP Package (#538)

This commit is contained in:
jenjac
2018-10-27 17:52:03 -05:00
committed by Victor Vrantchan
parent ca95cc11f3
commit 19970065c2
6 changed files with 553 additions and 0 deletions

111
vpp/client.go Normal file
View File

@@ -0,0 +1,111 @@
package vpp
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"path"
"github.com/pkg/errors"
)
const (
serverURL = "https://your.server.com" // This needs to be modified to be imported from server
version = "" // This needs to be modified to be imported from server
defaultBaseURL = "https://vpp.itunes.apple.com/WebObjects/MZFinance.woa/wa/VPPServiceConfigSrv"
mediaType = "application/json;charset=UTF8"
XServerProtocolVersionHeader = "X-Server-Protocol-Version"
XServerProtocolVersion = "3"
)
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// Contains the sToken string used to authenticate to the various VPP services
// Contains the return VPPServiceConfigSrv information
type Client struct {
SToken string
VPPServiceConfigSrv *VPPServiceConfigSrv
UserAgent string
Client HTTPClient
BaseURL *url.URL
}
func NewClient(sToken string) (*Client, error) {
baseURL, _ := url.Parse(defaultBaseURL)
c := Client{
SToken: sToken,
UserAgent: path.Join("micromdm", version),
Client: http.DefaultClient,
BaseURL: baseURL,
}
// Get VPPServiceConfigSrv Data
VPPServiceConfigSrv, err := c.GetVPPServiceConfigSrv()
if err != nil {
return nil, errors.Wrap(err, "create VPPServiceConfigSrv request")
}
c.VPPServiceConfigSrv = VPPServiceConfigSrv
// Set Client Context If Needed
context, err := c.GetClientContext()
if err != nil {
return nil, errors.Wrap(err, "GetClientContext request")
}
if context.HostName != serverURL {
_, err := c.SetClientContext(serverURL)
if err != nil {
return nil, errors.Wrap(err, "SetClientContext request")
}
}
return &c, nil
}
func (c *Client) newRequest(method, URLStr string, body interface{}) (*http.Request, error) {
rel, err := url.Parse(URLStr)
if err != nil {
return nil, errors.Wrapf(err, "parse vpp request url %s", URLStr)
}
u := c.BaseURL.ResolveReference(rel)
var buf bytes.Buffer
if body != nil {
if err := json.NewEncoder(&buf).Encode(body); err != nil {
return nil, errors.Wrap(err, "encode http body for VPP request")
}
}
req, err := http.NewRequest(method, u.String(), &buf)
if err != nil {
return nil, errors.Wrapf(err, "creating %s request to vpp %s", method, u.String())
}
req.Header.Add("User-Agent", c.UserAgent)
req.Header.Add("Content-Type", mediaType)
req.Header.Add("Accept", mediaType)
req.Header.Add(XServerProtocolVersionHeader, XServerProtocolVersion)
return req, nil
}
func (c *Client) do(req *http.Request, into interface{}) error {
resp, err := c.Client.Do(req)
if err != nil {
return errors.Wrap(err, "perform vpp request")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return errors.Errorf("unexpected vpp response. status=%d VPP API Error: %s", resp.StatusCode, string(body))
}
err = json.NewDecoder(resp.Body).Decode(into)
return errors.Wrap(err, "decode VPP response body")
}

152
vpp/clientconfigsrv.go Normal file
View File

@@ -0,0 +1,152 @@
package vpp
import (
"encoding/json"
"github.com/pkg/errors"
"github.com/satori/go.uuid"
"strings"
)
// Contains information that associates your particular mdm server to a VPP account token
type ClientContext struct {
HostName string `json:"hostname"`
GUID string `json:"guid"`
}
// Contains location information associated with a VPP account token
type Location struct {
LocationName string `json:"locationName"`
LocationID int `json:"locationId"`
}
// Contains org information associated with a VPP account token
type ClientConfigSrv struct {
ClientContext string `json:"clientContext"`
AppleID string `json:"appleId,omitempty"`
OrganizationIDHash string `json:"organizationIdHash"`
Status int `json:"status"`
OrganizationID int `json:"organizationId"`
UID string `json:"uId"`
CountryCode string `json:"countryCode"`
Location Location `json:"location"`
APNToken string `json:"apnToken"`
Email string `json:"email"`
}
// These specify options for the ClientConfigSrv
type GetClientConfigSrvOptions func(*getClientConfigSrvOpts) error
type getClientConfigSrvOpts struct {
SToken string `json:"sToken"`
Verbose bool `json:"verbose,omitempty"`
ClientContext string `json:"clientContext,omitempty"`
}
// Verbose is an optional argument that can be added to GetClientConfigSrv
func VerboseOption(verbose bool) GetClientConfigSrvOptions {
return func(opts *getClientConfigSrvOpts) error {
opts.Verbose = verbose
return nil
}
}
// ClientContext is an optional argument that can be added to GetClientConfigSrv
func ClientContextOption(context string) GetClientConfigSrvOptions {
return func(opts *getClientConfigSrvOpts) error {
opts.ClientContext = context
return nil
}
}
// Gets ClientConfigSrv information
func (c *Client) GetClientConfigSrv(opts ...GetClientConfigSrvOptions) (*ClientConfigSrv, error) {
// Set required and optional arguments
request := &getClientConfigSrvOpts{SToken: c.SToken}
for _, option := range opts {
if err := option(request); err != nil {
return nil, err
}
}
// Get the ClientConfigSrvURL
clientConfigSrvURL := c.VPPServiceConfigSrv.ClientConfigSrvURL
// Create the ClientConfigSrvURL request
req, err := c.newRequest("POST", clientConfigSrvURL, request)
if err != nil {
return nil, errors.Wrap(err, "create ClientConfigSrv request")
}
// Make the request
var response ClientConfigSrv
err = c.do(req, &response)
return &response, errors.Wrap(err, "make ClientConfigSrv request")
}
// Gets the appleID field along with the standard information
func (c *Client) GetClientConfigSrvVerbose() (*ClientConfigSrv, error) {
options := VerboseOption(true)
response, err := c.GetClientConfigSrv(options)
if err != nil {
return nil, errors.Wrap(err, "using verbose option")
}
return response, nil
}
// Gets the values that determine which mdm server is associated with a VPP account token
func (c *Client) GetClientContext() (*ClientContext, error) {
// Get the ClientConfigSrv info
clientConfigSrv, err := c.GetClientConfigSrv()
if err != nil {
return nil, errors.Wrap(err, "get ClientContext request")
}
// Get the ClientContext string
var context = clientConfigSrv.ClientContext
// Convert the string to a ClientContext type
var clientContext ClientContext
err = json.NewDecoder(strings.NewReader(context)).Decode(&clientContext)
if err != nil {
return nil, errors.Wrap(err, "decode ClientContext")
}
return &clientContext, nil
}
// Sets the values that determine which mdm server is associated with a VPP account token
func (c *Client) SetClientContext(serverURL string) (*ClientContext, error) {
// Generate a UUID that is tracked to ensure VPP licenses are up to date
uuid := uuid.NewV4().String()
// Generate a ClientContext string with the new UUID and the current serverURL
context := ClientContext{serverURL, uuid}
data, err := json.Marshal(context)
if err != nil {
return nil, errors.Wrap(err, "create new ClientContext")
}
newContext := string(data)
// Enter the new ClientContext string into the ClientConfigSrv options
options := ClientContextOption(newContext)
// Set the new ClientContext into the VPP account token
response, err := c.GetClientConfigSrv(options)
if err != nil {
return nil, errors.Wrap(err, "set ClientContext request")
}
// Get the new ClientContext string
var contextString = response.ClientContext
// Convert the string to a ClientContext type
var clientContext ClientContext
err = json.NewDecoder(strings.NewReader(contextString)).Decode(&clientContext)
if err != nil {
return nil, errors.Wrap(err, "decode new ClientContext")
}
return &clientContext, nil
}

89
vpp/getlicensessrv.go Normal file
View File

@@ -0,0 +1,89 @@
package vpp
import "github.com/pkg/errors"
// Contains information about the VPP Licenses associated with a VPP account token
type LicensesSrv struct {
IfModifiedSinceMillisOrig string `json:"ifModifiedSinceMillisOrig"`
TotalCount int `json:"totalCount"`
Status int `json:"status"`
TotalBatchCount string `json:"totalBatchCount"`
Licenses []License `json:"licenses"`
BatchToken string `json:"batchToken"`
BatchCount int `json:"batchCount"`
ClientContext string `json:"clientContext"`
UID string `json:"uId"`
Location Location `json:"location"`
}
// Contains information about VPP Licenses
type License struct {
LicenseID int `json:"licenseId"`
ProductTypeID int `json:"productTypeId"`
IsIrrevocable bool `json:"isIrrevocable"`
Status string `json:"status"`
PricingParam string `json:"pricingParam"`
AdamIDStr string `json:"adamIdStr"`
LicenseIDStr string `json:"licenseIdStr"`
ProductTypeName string `json:"productTypeName"`
AdamID int `json:"adamId"`
SerialNumber string `json:"serialNumber"`
}
// Options for the LicensesSrv
type GetLicensesSrvOptions struct {
SToken string `json:"sToken"`
SerialNumber string `json:"serialNumber,omitempty"`
}
// Gets the LicensesSrv information
func (c *Client) GetLicensesSrv(options GetLicensesSrvOptions) (*LicensesSrv, error) {
// Sends the sToken string
options.SToken = c.SToken
// Get the LicensesSrvURL
licensesSrvURL := c.VPPServiceConfigSrv.GetLicensesSrvURL
// Create the LicensesSrv request
req, err := c.newRequest("POST", licensesSrvURL, options)
if err != nil {
return nil, errors.Wrap(err, "create LicensesSrv request")
}
// Make the request
var response LicensesSrv
err = c.do(req, &response)
return &response, errors.Wrap(err, "make LicensesSrv request")
}
// Gets licenses with specified serial associated
func (c *Client) GetLicensesForSerial(serial string) ([]License, error) {
options := GetLicensesSrvOptions{
SerialNumber: serial,
}
response, err := c.GetLicensesSrv(options)
if err != nil {
return nil, err
}
licenses := response.Licenses
return licenses, err
}
// Checks if a particular serial is associated with an appID
func (c *Client) CheckAssignedLicense(serial string, appID string) (bool, error) {
// Get all licenses with serial associated
licenses, err := c.GetLicensesForSerial(serial)
if err != nil {
return false, err
}
// Check for the particular appID
for _, lic := range licenses {
if lic.AdamIDStr == appID {
return true, nil
}
}
return false, nil
}

74
vpp/getvppassetssrv.go Normal file
View File

@@ -0,0 +1,74 @@
package vpp
import "github.com/pkg/errors"
// Contains information about the VPP Assets associated with a VPP account token
type VPPAssetsSrv struct {
TotalCount int `json:"totalCount"`
Status int `json:"status"`
Assets []Asset `json:"assets"`
ClientContext string `json:"clientContext"`
UID string `json:"uId"`
Location Location `json:"location"`
}
// Contains information about VPP Assets
type Asset struct {
ProductTypeID int `json:"productTypeId"`
IsIrrevocable bool `json:"isIrrevocable"`
PricingParam string `json:"pricingParam"`
AdamIDStr string `json:"adamIdStr"`
ProductTypeName string `json:"productTypeName"`
DeviceAssignable bool `json:"deviceAssignable"`
}
// Gets information about the VPP Assets associated with a VPP Account token
func (c *Client) GetVPPAssetsSrv() (*VPPAssetsSrv, error) {
// Send the sToken string
request := struct {
SToken string `json:"sToken"`
}{
SToken: c.SToken,
}
// Get the VPPAssetsSrvURL
VPPAssetsSrvURL := c.VPPServiceConfigSrv.GetVPPAssetsSrvURL
// Create the VPPAssetsSrv request
req, err := c.newRequest("POST", VPPAssetsSrvURL, request)
if err != nil {
return nil, errors.Wrap(err, "create VPPAssetsSrv request")
}
// Make the request
var response VPPAssetsSrv
err = c.do(req, &response)
return &response, errors.Wrap(err, "get VPPAssetsSrv request")
}
// Gets the pricing param for a particular VPP asset
func (c *Client) GetPricingParamForApp(appID string) (string, error) {
// Get a list of assets
response, err := c.GetVPPAssetsSrv()
if err != nil {
return "", err
}
var assets = response.Assets
// Find the pricing param for the asset with matching appId
var pricing string
for _, asset := range assets {
if asset.AdamIDStr == appID {
pricing = asset.PricingParam
break
}
}
// Check for err finding Pricing Param
if pricing == "" {
err := errors.New("Unable to find Pricing Param")
return pricing, err
}
return pricing, nil
}

View File

@@ -0,0 +1,86 @@
package vpp
import "github.com/pkg/errors"
// Contains information about a managed license
type ManageVPPLicensesByAdamIdSrv struct {
ProductTypeID int `json:"productTypeId,omitempty"`
ProductTypeName string `json:"productTypeName,omitempty"`
IsIrrevocable bool `json:"isIrrevocable,omitempty"`
PricingParam string `json:"pricingParam,omitempty"`
UID string `json:"uId,omitempty,omitempty"`
AdamIdStr string `json:"adamIdStr,omitempty"`
Status int `json:"status"`
ClientContext string `json:"clientContext,omitempty"`
Location *Location `json:"location,omitempty"`
Associations []Association `json:"associations,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
ErrorNumber int `json:"errorNumber,omitempty"`
}
// Contains information about an app association
type Association struct {
SerialNumber string `json:"serialNumber"`
ErrorMessage string `json:"errorMessage,omitempty"`
ErrorCode int `json:"errorCode,omitempty"`
ErrorNumber int `json:"errorNumber,omitempty"`
LicenseIDStr string `json:"licenseIdStr,omitempty"`
LicenseAlreadyAssigned *License `json:"licenseAlreadyAssigned,omitempty"`
}
// Contains options to pass to the ManageVPPLicensesByAdamIdSrv
type ManageVPPLicensesByAdamIdSrvOptions struct {
SToken string `json:"sToken"`
AdamIDStr string `json:"adamIdStr"`
PricingParam string `json:"pricingParam"`
AssociateSerialNumbers []string `json:"associateSerialNumbers,omitempty"`
DisassociateSerialNumbers []string `json:"disassociateSerialNumbers,omitempty"`
}
// Associates a list of serials to a VPP app license
func (c *Client) AssociateSerialsToApp(appID string, serials []string) (*ManageVPPLicensesByAdamIdSrv, error) {
options := ManageVPPLicensesByAdamIdSrvOptions{
AssociateSerialNumbers: serials,
}
response, err := c.ManageVPPLicensesByAdamIdSrv(appID, options)
return &response, err
}
// Disssociates a list of serials to a VPP app license
func (c *Client) DisassociateSerialsToApp(appID string, serials []string) (*ManageVPPLicensesByAdamIdSrv, error) {
options := ManageVPPLicensesByAdamIdSrvOptions{
DisassociateSerialNumbers: serials,
}
response, err := c.ManageVPPLicensesByAdamIdSrv(appID, options)
return &response, err
}
// Interfaces with the ManageVPPLicensesByAdamIdSrv to managed VPP licenses
func (c *Client) ManageVPPLicensesByAdamIdSrv(appID string, options ManageVPPLicensesByAdamIdSrvOptions) (ManageVPPLicensesByAdamIdSrv, error) {
options.SToken = c.SToken
options.AdamIDStr = appID
// Get the pricing param required to manage a vpp license
pricing, err := c.GetPricingParamForApp(appID)
if err != nil {
return ManageVPPLicensesByAdamIdSrv{}, errors.Wrap(err, "get PricingParam request")
}
options.PricingParam = pricing
// Get the ManageVPPLicensesByAdamIdSrvURL
manageVPPLicensesByAdamIdSrvUrl := c.VPPServiceConfigSrv.ManageVPPLicensesByAdamIdSrvURL
// Create the ManageVPPLicensesByAdamIdSrv request
req, err := c.newRequest("POST", manageVPPLicensesByAdamIdSrvUrl, options)
if err != nil {
return ManageVPPLicensesByAdamIdSrv{}, errors.Wrap(err, "create ManageVPPLicensesByAdamIdSrv request")
}
// Make the Request
var response ManageVPPLicensesByAdamIdSrv
err = c.do(req, &response)
return response, errors.Wrap(err, "ManageVPPLicensesByAdamIdSrv request")
}

View File

@@ -0,0 +1,41 @@
package vpp
import "github.com/pkg/errors"
// Contains the most recent data from Apple for configuring vpp
type VPPServiceConfigSrv struct {
EditUserSrvURL string `json:"editUserSrvUrl"`
DisassociateLicenseSrvURL string `json:"disassociateLicenseSrvUrl"`
ContentMetadataLookupURL string `json:"contentMetadataLookupUrl"`
ClientConfigSrvURL string `json:"clientConfigSrvUrl"`
GetUserSrvURL string `json:"getUserSrvUrl"`
GetUsersSrvURL string `json:"getUsersSrvUrl"`
GetLicensesSrvURL string `json:"getLicensesSrvUrl"`
GetVPPAssetsSrvURL string `json:"getVPPAssetsSrvUrl"`
VppWebsiteURL string `json:"vppWebsiteUrl"`
InvitationEmailURL string `json:"invitationEmailUrl"`
RetireUserSrvURL string `json:"retireUserSrvUrl"`
AssociateLicenseSrvURL string `json:"associateLicenseSrvUrl"`
ManageVPPLicensesByAdamIdSrvURL string `json:"manageVPPLicensesByAdamIdSrvUrl"`
RegisterUserSrvURL string `json:"registerUserSrvUrl"`
MaxBatchAssociateLicenseCount int `json:"maxBatchAssociateLicenseCount"`
MaxBatchDisassociateLicenseCount int `json:"maxBatchDisassociateLicenseCount"`
Status int `json:"status"`
ErrorCodes []Error `json:"errorCodes"`
}
type Error struct {
ErrorMessage string `json:"errorMessage"`
ErrorNumber int `json:"errorNumber"`
}
func (c *Client) GetVPPServiceConfigSrv() (*VPPServiceConfigSrv, error) {
var response VPPServiceConfigSrv
req, err := c.newRequest("GET", c.BaseURL.String(), nil)
if err != nil {
return nil, errors.Wrap(err, "create VPPServiceConfigSrv request")
}
err = c.do(req, &response)
return &response, errors.Wrap(err, "VPPServiceConfigSrv request")
}