# frozen_string_literal: true require_relative "ruby_ws_one/version" require 'httparty' require 'json' require 'finest/builder' require 'active_support/core_ext/module/attribute_accessors' module RubyWsOne mattr_accessor :aw_tenant_code, :domain mattr_accessor :token_url, default: 'https://uat.uemauth.vmwservices.com/connect/token' mattr_accessor :version, default: 2 # As system so far we've got: mdm, system mattr_reader :rest_url, default: 'https://%s.awmdm.com/api/%s/%s/%s/%s/%s%s/?%s' # # Setup data from initializer # domain - subdomain in workspace one where your tenant is hosted # token_url - Localized url to get a Bearer token https://kb.vmware.com/s/article/76967 # aw_tenant_code - OAuth tenant code that identifies your client # version - default API version # # Workspace ONE setup guide # https://docs.vmware.com/en/VMware-Workspace-ONE-UEM/services/UEM_ConsoleBasics/GUID-BF20C949-5065-4DCF-889D-1E0151016B5A.html def self.setup yield(self) end # # Request is the base class that manages basic calls in order to get a Brearer token as well as the # generic http_method call that manages all request back and forth from workspace one API. # class Request include Finest::Builder include HTTParty format :json attr_reader :system attr_accessor :client_id, :client_secret, :aw_tenant_code, :domain def initialize(json = {}) super(json) @client_secret ||= json[:client_secret] @client_id ||= json[:client_id] end # # Get a Bearer token from one of the Region-Specific Token URLs and keeps it in cache. # Token will expire in 30 minutes and will acquire one on the very next call. # # The token URL region has to match where the area your tenant is hosted, otherwise this call will return # a invalid_request error. # def token Rails.cache.fetch(@client_id, expires_in: 30.minutes) do JSON.parse( http_method_token( body: { grant_type: :client_credentials, client_id: @client_id, client_secret: @client_secret } )&.body )&.transform_keys(&:to_sym) end end def http_method(args = {}) _headers = { authorization: "Bearer #{token[:access_token]}", 'aw-tenant-code': RubyWsOne.aw_tenant_code, 'Content-Type': 'application/json', Accept: "application/json;version=#{args.fetch(:version, 2)}" } _url = RubyWsOne.rest_url % { domain: RubyWsOne.domain || args[:domain], system: args.fetch(:system, @system), element: args.fetch(:element, 'devices'), id: args.fetch(:id, nil), action: args.fetch(:action, nil), command: args.fetch(:command, nil), action_id: args.fetch(:action_id, nil), filter: args.fetch(:filter, {}).to_query } self.class.method(args.fetch(:method, :get)).call(_url, body: args.fetch(:body, {}).to_json, headers: _headers) rescue StandardError => e { error: e&.message || 'Error connecting to manager service provider', status: :not_found } end # Parses the response from workspace one API using a open struct approach to make easier to access the data. # It will return an instance of the object requested, ie. Device,User # # As part of the response will also include the client_id as well as client_secret to be able to call the API # on every object. # user = RubyWsOne::User.new(@credentials).search(filter: { email: 'noreply@rzilient.club' }) # resp = user.first.delete(true) # def transform(res, args) _resp = JSON.parse(res.body.presence || {}.to_s).transform_keys(&:downcase) return self.class.new(_resp.merge(get_instance_variables)) unless _resp[args[:element]&.to_s] Array.wrap(_resp[args[:element]&.to_s]).map! do |elem| self.class.new(elem.transform_keys(&:downcase).merge(get_instance_variables)) end end private # # Getting current instance variable to be merge along with the response coming from Workspace ONE API response. # def get_instance_variables instance_variables.to_h { |name| [name.to_s.delete('@'), instance_variable_get(name)] } end # # Generic http method call to get the bearer token # def http_method_token(args) self.class.method(args.fetch(:method, :post)).call(RubyWsOne.token_url, body: args.fetch(:body, nil)) end end class Device < Request def initialize(json = {}) super(json) @system ||= :mdm end def call(args = {}) transform(http_method(args.merge!(element: :devices)), args) end def search(args = {}) transform(http_method(args.merge!(action: __method__, element: :devices)), args) end def lock commands(command: :lock) end def lostmode commands(command: :clearpasscode) end def reboot; end def wipe commands(command: :devicewipe) end def activate; end def retire transform(http_method(args.merge!(method: :delete, id: id&.value, element: :devices)), {}) end def phone? %w[Android Apple WindowsMobile WindowsPhone8 BlackBerry10 WindowsPhone Symbian].include?(platform) end # [Lock, EnterpriseWipe, DeviceWipe, DeviceQuery, ClearPasscode, SyncDevice, StopAirPlay]. def commands(args = {}) transform( http_method( args.merge!( method: :post, action: __method__, id: args.fetch(:version, 2) == 2 ? uuid : id&.value, element: :devices ) ), args ) end end class User < Request def initialize(json = {}) super(json) @system ||= :system end def adduser(args = {}) transform(http_method(args.merge!(method: :post, action: __method__, element: :users)), args) end def search(args = {}) transform(http_method(args.merge!(action: __method__, element: :users)), args) end def deactivate(args = {}) transform(http_method(args.merge!(method: :post, action: __method__, id: id&.value, element: :users)), args) end def activate(args = {}) transform(http_method(args.merge!(method: :post, action: __method__, id: id&.value, element: :users)), args) end def delete(confirm) return unless confirm transform(http_method(method: :delete, action: __method__, id: id&.value, element: :users), {}) end end end