# encoding: utf-8 #require 'httpclient' #require 'json' require 'date' require 'cgi' require_relative 'response' require_relative 'communication' require_relative 'urls' require_relative 'groups' require_relative 'dto' require_relative 'directories' require_relative 'pst-converter' require_relative 'resource' require_relative 'resources/account' require_relative 'resources/domain' require_relative 'resources/group' require_relative 'email-sync' module MxHero::API # A client to interact with mxhero engine API class Client include Communication include Urls # @param [Hash] config the options of configuration # @option config [String] :api_url The URL to consume the API # @option config [String] :username The username for access the API # @option config [String] :password The password for the user that access the API # @option config [Boolean] :verbose (false) If true puts information about http operations # @option config [String] :as_user Send to the API to indentify the end user (app user email) def initialize(config = {}) @service_url = config[:api_url] @username = config[:username] @password = config[:password] @verbose = config[:verbose] || false @as_user = config[:as_user] end # Expose directories api # # @return MxHero::API::Directories def directories(domain) @directories ||= Directories.new(domain, api_url: @service_url, username: @username, password: @password, verbose: @verbose, as_user: @as_user) end # Find a rule by domain and ID # # @param domain [String] # @param id [Integer] the rule id # # @return [Hash, nil] the Rule information or nil if not exist # # @raise an exception when the status code isn't 200 def domain_rule(domain, id) url = domain_rule_url(domain, id) response = call(:get, url) raise 'an error ocurred when try to communicate with the API' if response.status != 200 json_parse(response.content) end # Update a rule # @return [MxHero::API::Response] When the rule is update correctly then return MxHero::API::Response object without msg (msg nil) # In case of error, the message is completed with the cause of error def update_rule(rule) url = domain_rule_url(rule[:domain], rule[:id]) response = call(:put, url, rule.to_json) parse_response(response) end # In case of error, the message is completed with the cause of error def rule_status(domain, rule_id, enabled = true) url = domain_rule_url(domain, rule_id)+"/status?enabled=#{enabled}" response = call(:put, url) parse_response(response) end # Retrive all the domains # # TODO: Improve the response. We really need manage pagination here? # # @return [Hash] with the list of domains # * :elements [Array] the list of domains as a Hash, when any element contains: # * :domain [String] # * :server [String] # * :creationDate [Fixnum] # * :updateDate [Fixnum] # * :aliases # * :ldap # * :totalElements # * :totalPages # * :actualPage # # @raise an exception when the status code isn't 200 def domains(params = {}) url = domains_url limit, offset = params.values_at(:limit, :offset) url = paginate(domains_url, { limit: limit, offset: offset }) if limit && offset response = call(:get, url) raise 'an error ocurred when try to communicate with the API' if response.status != 200 response_as_a_hash = json_parse(response.content) response_as_a_hash end # Move COS to trial # @return true | false def move_to_trial(domain) url = domain_by_id_url(domain) + '/cos/trial' response = call(:put, url) response.status == 200 end # Create a new domain base on domain_obj hash # # @param domain_obj The domain to be created. For example: # [Hash] with detailed domain # * :domain [String] # * :server [String] # * :inbound [Boolean] # * :outbound [Boolean] # * :features [Array] # * :cos [Hash] # * :source [String] (gapps|on_premise|ms_agent|office365) # * :aliases [Array] # * :ldap [Hash] # # @return [Boolean] true if was successfully created # def create_domain(domain_obj = {}) response = call(:post, domains_url, domain_obj.to_json, throw_exception: false) response.status == 201 end def update_domain(domain, domain_obj = {}) response = call(:put, domain_by_id_url(domain), domain_obj.to_json, throw_exception: false) response.status == 200 end # Create a new domain base on domain_obj hash # # @param domain_obj The domain to be created. For example: # [Hash] with detailed domain # * :domain [String] # * :server [String] # * :inbound [Boolean] # * :outbound [Boolean] # * :features [Array] # * :cos [Hash] # * :source [String] (gapps|on_premise|ms_agent|office365) # * :aliases [Array] # * :ldap [Hash] # # @return [Boolean] true if was successfully created # def add_feature(domain_name, feature_component) feature = { feature: feature_component, maxRulesAmount: 1 } response = call(:post, features_url(domain_name), feature.to_json, throw_exception: false) response.status == 200 end # Retrive the domain information # # @param name The domain name or id. For example: 'mydomain.com' # @return [Domain] or nil if not found # # @raise an exception when the status code is not 200 (ok) or 404 (not found) # def domain(name) domain_info = fetch domain_by_id_url(name), on_error: "An error ocurred when try to fetch the domain #{name}." return nil unless domain_info Domain.new domain_info end #def update_cos(domain, cos) # domain_by_id_url(name) + "cos/#{cos}" # response = call(:post, url, msg.to_json, throw_exception: false) #end # Fetch the LDAP information of the domain # # @return [Hash] with # * :domain # * :directoryType # * :addres # * :port # * :sslFlag [Boolean] # * :user # * :password # * :filter # * :base # * :nextUpdate # * :lastUpdate # * :error # * :overrideFlag # * :dnAuthenticate # * :properties [Array] # def ldap_info(domain) fetch domain_by_id_url(domain) + '/adldap', on_error: "An error was ocurred when try to fecht the ldap information of #{domain}" end # # Retrive all the account from one domain # # @params [String] domain # @params [String] filter_account the account to filter in the list. This can be a part of an account. # @param [Hash] refinement # @option refinement [Fixnum] :limit of elements per page # @option refinement [Fixnum] :offset number of page (start in 1) # @option refinement [String] :account filter accounts that start with this value # @option refinement [Boolean] :without_group filter accounts without group # # # @return [Hash] with the following elements: # * :elements [Array] the list of accounts as a Hash, when any element contains: # * :account [String] example: alex # * :domain [String] example: mxhero.com # * :createdDate [Fixnum] example: 1375909565000 (epoch format) # * :updatedDate [Fixnum] # * :group [String] # * :aliases [Array] the list of aliases of one account # * :name [String] # * :domain [String] # * :dataSource [String] # * :dataSource [String] # * :totalElements [Fixnum] # * :totalPages [Fixnum] # * :actualPage [Fixnum] # # @raise an exception when the status code isn't 200 def accounts_by_domain(domain, refinement = {}) #filter_account = nil, pagination = {}) params = refinement.dup filter_account = params.delete(:account) filter_account = CGI::escape(filter_account) if filter_account without_group = params.delete(:without_group) || false limit, offset = params.values_at(:limit, :offset) if without_group url = accounts_without_group_url(domain, filter_account) else url = paginate accounts_by_domain_url(domain, filter_account), { limit: limit, offset: offset } end response = call(:get, url) json_parse(response.content) end def accounts_without_group_url(domain, filter_account) domain_by_id_url(domain) + "/groups/accounts/available?account=#{filter_account}" end def verbose? @verbose end def verbose=(verbose) @verbose = verbose end # @param domain [String] # @param [Hash] msg # @option msg [String] :domain # @option msg [Boolean] :twoWays # @option msg [Boolean] :enabled # @option msg [String] :name # @option msg [Integer] :created in epoch format # @option msg [Hash] :fromDirection # @option msg [Hash] :toDirection # @option msg [Array] :properties # @option msg [String] :component # # @return [MxHero::API::Response] def create_rule_for_domain(domain, msg) url = rules_for_domain_url(domain) response = call(:post, url, msg.to_json, throw_exception: false) parse_response(response) end ## Create a rule ## ## @param [Hash] msg ## @option msg [String] :domain ## @option msg [Boolean] :twoWays ## @option msg [Boolean] :enabled ## @option msg [String] :name ## @option msg [Integer] :created in epoch format ## @option msg [Hash] :fromDirection ## @option msg [Hash] :toDirection ## @option msg [Array] :properties ## @option msg [String] :component ## ## [MxHero::API::Response] #def create_rule(msg) # response = call(:post, rules_url, msg.to_json, throw_exception: false) # parse_response(response) #end # Retrieve all the rules for one domain # # @param [String] domain # @param [String] component Filter the list of rules by this component [optional] # # @return [MxHero::API::Response] reponse # the key :msg contains an array of rules for the domain. def rules_for_domain(domain, component = nil) url = rules_for_domain_url(domain) url << "?component=#{component}" if component response = call(:get, url) parse_response(response, on_empty: []) end # @return [Boolean] true when operation it's ok def delete_rule(domain, id) url = domain_rule_url(domain, id) response = call(:delete, url) return true if response.status == 200 return false end # @param [String] domain # @param [String] account Ex.: test or mxhero (the user name) # @return [MxHero::API::Response] reponse # the key :msg contains a Hash with the key, value of any property. # Example: # # { 'email': 'test@mxhero.com', 'name': 'John', 'lastname': 'Doe', ... } # def account_properties(domain, account) url = account_properties_url(domain, account) response = call(:get, url) if response.status == 200 props = {} json_parse(response.content).each { |property| props[property[:name]] = property[:value] } return Response.new(response.code, props) end parse_response(response) end # @param [String] domain # @param [String] account # @param [Hash] properties The properties of the account. Ex.: { 'email': 'test@mxhero.com', 'name': 'John', 'lastname': 'Doe', ... } # # @return [MxHero::API::Response]. On success not return msg. def update_account_properties(domain, account, properties) url = account_properties_url(domain, account) response = call(:put, url, account_properties_to_json(properties)) parse_response(response) end # # @param [String] domain # @param [Array] accounts. An array of hash with :account and :properties. The :properties # contains a Hash with the equivalent of name and value. # @param [String] scope :groups, :properties or :both (default: properties) # def update_accounts(domain, accounts, scope = :properties) scope_param = scope == :both ? 'groups,properties' : scope.to_s #url = "/domains/#{domain}/accounts/upload?scope=#{scope_param}" url = accounts_by_domain_url(domain) + "/upload?scope=#{scope_param}" message = [] accounts.each do |account| properties = remap_properties(account[:properties]) message << { account: account[:account], properties: properties, group: account[:group], domain: domain } #message << { account: account[:account], properties: properties, group: account[:group] } end response = call(:put, url, message.to_json) # accounts to json parse_response(response) end # @return [Hash|String|nil] can return a hash with the key and value of any # system property. If use a parameter (key) return the string of this value. # In the two cases return nil when not found values # def system_properties(key = nil) response = call(:get, system_properties_url(key)) if response.status == 200 parsed = json_parse(response.content) if parsed.is_a? Array props = {} parsed.each { |property| props[property[:key]] = property[:value] } return props else return parsed[:value] end end nil end # Update or create a system property def save_system_property(key, value) property = { key: key, value: value }.to_json response = if system_properties(key).nil? call(:post, system_properties_url, property) else call(:put, system_properties_url(key), property) end parse_response(response).success? end # Obtain the list of users admin in a specific domain # @return [Array|nil] when there is no users for that domain. # If the domain doesn't exist then return nil def users_for_domain(domain) users = nil response = call(:get, users_for_domain_url(domain), throw_exception: false) if response.status == 200 users = json_parse(response.content) end users end # Obtain the list of domains to one user # @return [Array|nil] when the user exist but don't have domains the list is empty. # If the user isn't exist then return nil def user_domains(user) domains = nil response = call(:get, user_domains_url(user)) if response.status == 200 content = json_parse(response.content) domains = content.map { |info| info[:domain] } end domains end # Fetch the user # @return [MxHero::API::Response | nil] def fetch_user(user) response = call(:get, user_url(user)) if response.status == 200 return parse_response(response) end nil end # Create a new user # @param [Hash] user_info # @option user_info [String] :name # @option user_info [String] :lastName # @option user_info [String] :notifyEmail # @option user_info [String] :userName # @option user_info [String] :locale # @option user_info [String] :password # # @param [Array] domains # def create_user(user_info, *domains) user = { name: "", lastName: "", notifyEmail: "", userName: "", locale: "en_US", password: "1234", authorities: ["ROLE_DOMAIN_ADMIN"], created: DateTime.now.strftime('%Q'), domains: domains.map { |domain| { domain: domain } } } user.merge!(user_info) response = call(:post, users_url, user.to_json, throw_exception: false) response.status == 201 end # Associate domains with an existing user # @param [String] user # @param [String] domains # def associate_user_domain(user, domain) domain_obj = { domain: domain } response = call(:post, user_domains_url(user), domain_obj.to_json, throw_exception: false) response.status == 200 end # Unassociate specific domain from an user admin # @param [String] user # @param [String] domain # def unassociate_user_domain(user, domain) response = call(:delete, user_with_domain_url(user,domain), throw_exception: false) response.status == 200 end # Update user # @param [Hash] user retrieve to update # # @param user_name [String] # def update_user(user, user_name) response = call(:put, user_url(user_name), user.to_json, throw_exception: false) parse_response(response) end # Validate if the an user and password match # def valid_user_credentials?(user, password) validate_user_credential_url = user_url(user) + "password/#{CGI::escape(password)}" response = call(:get, validate_user_credential_url) response.status == 204 end def set_new_password(user_name, new_password) response = call(:put, user_url_set_password(user_name, new_password), throw_exception: false) response.status == 200 end def reset_password(user_name) response = call(:get, user_url_reset_password(user_name), throw_exception: false) response.status == 200 end # test_msg example: # { # "matching":{ # "matcher": [ # { # "default":false, # "type":"regex", # "regex":"(.*)@(.*)", # "toValue":"${2}/${1}/${0}" # } # ], # "placeholder":"email" # }, # "value":"smith@example.org", # "key":"email" # } # # Response # HTTP: 200 OK # {"result":"example.org/smith/smith@example.org"} # # On error, response: # HTTP Status 500 # { # "status": 500, # "code": 500, # "developerMessage": "Dangling meta character '*' near index 0\n*.(.*)@(.*)\n^", # "moreInfoUrl": "mailto:support@mxhero.com" # } def regex_tester(test_msg) response = call(:put, service_url + '/regex/check/pattern', test_msg.to_json, throw_exception: false) parse_response(response) end # -------------------------------------------------------------------------------------------------------------------------------- private def users_url service_url + "/users" end def user_url(user) users_url + "/#{user}/" end def user_domains_url(user) user_url(user) + "domains" end def users_for_domain_url(domain) users_url + "/domain/#{domain}/" end def user_with_domain_url(user,domain) user_url(user) + "domains/#{domain}/" end def user_url_set_password(user,new_password) pass_esc = CGI::escape(new_password) users_url + "/#{user}/setPassword?newPassword=#{pass_esc}" end def user_url_reset_password(user) pass_esc = CGI::escape(user) users_url + "/resetPassword?email=#{pass_esc}" end # Fetch an element from an URL. That element maybe not found # @return a Hash when the element exist. Or return nil when not found. # # @raise RuntimeError when an error ocurrs. def fetch(url, msg) response = call(:get, url) return json_parse(response.content) if response.ok? return nil if response.code == 404 error = msg[:on_error] + " HTTP code: #{response.code}" if response.content.include?('message') hash = json_parse(response.content) error << " Response message: #{hash[:message]}" end raise error end def remap_properties(properties) return nil if properties.nil? properties.keys.map { |name| { name: name, value: properties[name] } } end # Complete the URL with the pagination info (:limit and :offset) # @param [String] original URL. Ex.: http://www.example.com/api/accounts # @param [Hash] pagination # @option pagination [Fixnum] :limit of elements per page # @option pagination [Fixnum] :offset number of page (start in 1) # # @return [String] the URL with the pagination parameters. # Ex.: url: http://www.example.com/api/accounts # pagination: { limit: 4, offset: 2 } # return > http://www.example.com/api/accounts?limit=4&offset=2 def paginate(url, pagination) paginated = url.dup connector = url.include?('?') ? '&' : '?' [ :limit, :offset ].map do |elem| if pagination.key? elem paginated << "#{connector}#{elem.to_s}=#{pagination[elem]}" connector = '&' end end paginated end def account_properties_to_json(properties) out = [] properties.each do |key, value| out << { name: key, value: value } end out.to_json end def rules_for_domain_url(domain) service_url + "/domains/#{domain}/rules" end def rules_url service_url + '/rules' end def accounts_by_domain_url(domain, filter_account = nil) filter = filter_account ? "?account=#{filter_account}" : '' domain_by_id_url(domain) + 'accounts' + filter end def domain_rule_url(domain, id) rules_for_domain_url(domain) + "/#{id}" end def features_url(domain) domain_by_id_url(domain) + "/features" end def account_properties_url(domain, account) accounts_by_domain_url(domain) + "/#{account}/properties" end def system_properties_url(key = nil) fixed_key = key.nil? ? nil : "#{key}/" service_url + "/system/properties/#{fixed_key}" end # @return [MxHero::API::Response] a response object def parse_response(response, opts = { on_empty: nil }) json = response.content hash = json.nil? || json.empty? ? opts[:on_empty] : json_parse(json) Response.new(response.code, hash) end end end