class DynamicsCRM::Client

Constants

OCP_LOGIN_URL
REGION

Attributes

caller_id[RW]
hostname[R]
logger[RW]
login_url[R]
organization_endpoint[R]
region[R]
timeout[RW]

Public Class Methods

new(config={organization_name: nil, hostname: nil, caller_id: nil, login_url: nil, region: nil}) click to toggle source

Initializes Client instance. Requires: organization_name Optional: hostname

# File lib/dynamics_crm/client.rb, line 40
def initialize(config={organization_name: nil, hostname: nil, caller_id: nil, login_url: nil, region: nil})
  raise RuntimeError.new("organization_name or hostname is required") if config[:organization_name].nil? && config[:hostname].nil?

  @organization_name = config[:organization_name]
  @hostname = config[:hostname] || "#{@organization_name}.api.crm.dynamics.com"
  @organization_endpoint = "https://#{@hostname}/XRMServices/2011/Organization.svc"
  REGION.default = @organization_endpoint
  @caller_id = config[:caller_id]
  @timeout = config[:timeout] || 120

  # The Login URL and Region are located in the client's Organization WSDL.
  # https://tinderboxdev.api.crm.dynamics.com/XRMServices/2011/Organization.svc?wsdl=wsdl0
  #
  # Login URL: Policy -> Issuer -> Address
  # Region: SecureTokenService -> AppliesTo
  @login_url = config[:login_url]
  @region = config[:region] || determine_region
end

Public Instance Methods

associate(entity_name, guid, relationship, related_entities) click to toggle source
# File lib/dynamics_crm/client.rb, line 190
def associate(entity_name, guid, relationship, related_entities)
  request = associate_request(entity_name, guid, relationship, related_entities)
  xml_response = post(organization_endpoint, request)
  Response::AssociateResponse.new(xml_response)
end
authenticate(username, password) click to toggle source

Public: Authenticate User

Examples

client.authenticate('test@orgnam.onmicrosoft.com', 'password')
# => true || raised Fault

Returns true on success or raises Fault

# File lib/dynamics_crm/client.rb, line 67
def authenticate(username, password)
  @username = username
  @password = password

  auth_request = if on_premise?
    build_on_premise_request(username, password, region, login_url)
  else
    build_ocp_request(username, password, region, login_url)
  end

  soap_response = post(login_url, auth_request)

  document = REXML::Document.new(soap_response)
  # Check for Fault
  fault_xml = document.get_elements("//[local-name() = 'Fault']")
  raise XML::Fault.new(fault_xml) if fault_xml.any?

  if on_premise?
    @security_token0 = document.get_elements("//e:CipherValue").first.text.to_s
    @security_token1 = document.get_elements("//xenc:CipherValue").last.text.to_s
    @key_identifier = document.get_elements("//o:KeyIdentifier").first.text
    @cert_issuer_name = document.get_elements("//X509IssuerName").first.text
    @cert_serial_number = document.get_elements("//X509SerialNumber").first.text
    @server_secret = document.get_elements("//trust:BinarySecret").first.text

    @header_current_time = get_current_time
    @header_expires_time = get_current_time_plus_hour
    @timestamp = '<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0"><u:Created>' + @header_current_time + '</u:Created><u:Expires>' + @header_expires_time + '</u:Expires></u:Timestamp>'
    @digest_value = Digest::SHA1.base64digest @timestamp
    @signature = '<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"></SignatureMethod><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod><DigestValue>' + @digest_value + '</DigestValue></Reference></SignedInfo>'
    @signature_value = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), Base64.decode64(@server_secret), @signature)).chomp
  else
    cipher_values = document.get_elements("//CipherValue")

    if cipher_values && cipher_values.length > 0
      @security_token0 = cipher_values[0].text
      @security_token1 = cipher_values[1].text
      # Use local-name() to ignore namespace.
      @key_identifier = document.get_elements("//[local-name() = 'KeyIdentifier']").first.text
    else
      raise RuntimeError.new(soap_response)
    end
  end

  true
end
create(entity_name, attributes) click to toggle source

These are all the operations defined by the Dynamics WSDL. Tag names are case-sensitive.

# File lib/dynamics_crm/client.rb, line 116
def create(entity_name, attributes)
  entity = XML::Entity.new(entity_name)
  entity.attributes = XML::Attributes.new(attributes)

  xml_response = post(organization_endpoint, create_request(entity))
  Response::CreateResult.new(xml_response)
end
create_attachment(entity_name, entity_id, options={}) click to toggle source
# File lib/dynamics_crm/client.rb, line 202
def create_attachment(entity_name, entity_id, options={})
  raise "options must contain a document entry" unless options[:document]

  file_name = options[:filename]
  document = options[:document]
  subject = options[:subject]
  text = options[:text] || ""

  if document.is_a?(String) && File.exists?(document)
    file = File.new(document)
  elsif document.is_a?(String) && document.start_with?("http")
    require 'open-uri'
    file = open(document)
  else
    file = document
  end

  if file.respond_to?(:base_uri)
    file_name ||= File.basename(file.base_uri.path)
    mime_type = MimeMagic.by_path(file.base_uri.path)
  elsif file.respond_to?(:path)
    file_name ||= File.basename(file.path)
    mime_type = MimeMagic.by_path(file.path)
  else
    raise "file must be a valid File object, file path or URL"
  end

  documentbody = file.read
  attributes = {
    objectid: {id: entity_id, logical_name: entity_name},
    subject: subject || file_name,
    notetext: text || "",
    filename: file_name,
    isdocument: true,
    documentbody: ::Base64.encode64(documentbody),
    filesize: documentbody.length,
    mimetype: mime_type
  }

  self.create("annotation", attributes)
end
delete(entity_name, guid) click to toggle source
# File lib/dynamics_crm/client.rb, line 175
def delete(entity_name, guid)
  request = delete_request(entity_name, guid)

  xml_response = post(organization_endpoint, request)
  Response::DeleteResponse.new(xml_response)
end
disassociate(entity_name, guid, relationship, related_entities) click to toggle source
# File lib/dynamics_crm/client.rb, line 196
def disassociate(entity_name, guid, relationship, related_entities)
  request = disassociate_request(entity_name, guid, relationship, related_entities)
  xml_response = post(organization_endpoint, request)
  Response::DisassociateResponse.new(xml_response)
end
execute(action, parameters={}, response_class=nil) click to toggle source
# File lib/dynamics_crm/client.rb, line 182
def execute(action, parameters={}, response_class=nil)
  request = execute_request(action, parameters)
  xml_response = post(organization_endpoint, request)

  response_class ||= Response::ExecuteResult
  response_class.new(xml_response)
end
fetch(fetchxml) click to toggle source
# File lib/dynamics_crm/client.rb, line 156
def fetch(fetchxml)
  response = self.execute("RetrieveMultiple", {
    Query: XML::FetchExpression.new(fetchxml)
  })
  response['EntityCollection']
end
load_entity(logical_name, id) click to toggle source
# File lib/dynamics_crm/client.rb, line 290
def load_entity(logical_name, id)
  case logical_name
  when "opportunity"
    Model::Opportunity.new(id, self)
  else
    Model::Entity.new(logical_name, id, self)
  end
end
retrieve(entity_name, guid, columns=[]) click to toggle source

crmtroubleshoot.blogspot.com.au/2013/07/dynamics-crm-2011-php-and-soap-calls.html

# File lib/dynamics_crm/client.rb, line 125
def retrieve(entity_name, guid, columns=[])
  column_set = XML::ColumnSet.new(columns)
  request = retrieve_request(entity_name, guid, column_set)

  xml_response = post(organization_endpoint, request)
  Response::RetrieveResult.new(xml_response)
end
retrieve_all_entities() click to toggle source

Metadata Calls EntityFilters Enum: Default, Entity, Attributes, Privileges, Relationships, All

# File lib/dynamics_crm/client.rb, line 250
def retrieve_all_entities
  response = self.execute("RetrieveAllEntities", {
    EntityFilters: "Entity",
    RetrieveAsIfPublished: true
    },
    Metadata::RetrieveAllEntitiesResponse)
end
retrieve_attachments(entity_id, columns=["filename", "documentbody", "mimetype"]) click to toggle source
# File lib/dynamics_crm/client.rb, line 244
def retrieve_attachments(entity_id, columns=["filename", "documentbody", "mimetype"])
  self.retrieve_multiple("annotation", [["objectid", "Equal", entity_id], ["isdocument", "Equal", true]], columns)
end
retrieve_attribute(entity_logical_name, logical_name) click to toggle source
# File lib/dynamics_crm/client.rb, line 269
def retrieve_attribute(entity_logical_name, logical_name)
  self.execute("RetrieveAttribute", {
    EntityLogicalName: entity_logical_name,
    LogicalName: logical_name,
    MetadataId: "00000000-0000-0000-0000-000000000000",
    RetrieveAsIfPublished: true
    },
    Metadata::RetrieveAttributeResponse)
end
retrieve_entity(logical_name, entity_filter="Attributes") click to toggle source

EntityFilters Enum: Default, Entity, Attributes, Privileges, Relationships, All

# File lib/dynamics_crm/client.rb, line 259
def retrieve_entity(logical_name, entity_filter="Attributes")
  self.execute("RetrieveEntity", {
    LogicalName: logical_name,
    MetadataId: "00000000-0000-0000-0000-000000000000",
    EntityFilters: entity_filter,
    RetrieveAsIfPublished: true
    },
    Metadata::RetrieveEntityResponse)
end
retrieve_metadata_changes(entity_query) click to toggle source
# File lib/dynamics_crm/client.rb, line 279
def retrieve_metadata_changes(entity_query)
  self.execute("RetrieveMetadataChanges", {
    Query: entity_query
  },
  Metadata::RetrieveMetadataChangesResponse)
end
retrieve_multiple(entity_name, criteria = [], columns = [], operator = nil) click to toggle source

Suports parameter list or QueryExpression object.

# File lib/dynamics_crm/client.rb, line 142
def retrieve_multiple(entity_name, criteria = [], columns = [], operator = nil)
  if entity_name.is_a?(XML::QueryExpression)
    query = entity_name
  else
    query = XML::QueryExpression.new(entity_name)
    query.columns = columns
    query.criteria = XML::Criteria.new(criteria, filter_operator: operator)
  end

  request = retrieve_multiple_request(query)
  xml_response = post(organization_endpoint, request)
  Response::RetrieveMultipleResult.new(xml_response)
end
rollup(target_entity, query, rollup_type="Related") click to toggle source
# File lib/dynamics_crm/client.rb, line 133
def rollup(target_entity, query, rollup_type="Related")
  self.execute("Rollup", {
    Target: target_entity,
    Query: query,
    RollupType: rollup_type
  })
end
update(entity_name, guid, attributes) click to toggle source

Update entity attributes

# File lib/dynamics_crm/client.rb, line 164
def update(entity_name, guid, attributes)

  entity = XML::Entity.new(entity_name)
  entity.id = guid
  entity.attributes = XML::Attributes.new(attributes)

  request = update_request(entity)
  xml_response = post(organization_endpoint, request)
  Response::UpdateResponse.new(xml_response)
end
who_am_i() click to toggle source
# File lib/dynamics_crm/client.rb, line 286
def who_am_i
  self.execute('WhoAmI')
end

Protected Instance Methods

determine_region() click to toggle source
# File lib/dynamics_crm/client.rb, line 318
def determine_region
  hostname.match(/(crm\d?\.dynamics.com)/)
  REGION[$1]
end
formatter() click to toggle source
# File lib/dynamics_crm/client.rb, line 355
def formatter
  unless @formatter
    @formatter = REXML::Formatters::Pretty.new(2)
    @formatter.compact = true # This is the magic line that does what you need!
  end
  @formatter
end
log_xml(title, xml) click to toggle source
# File lib/dynamics_crm/client.rb, line 345
def log_xml(title, xml)
  return unless logger

  logger.debug(title)

  doc = REXML::Document.new(xml)
  formatter.write(doc.root, logger)
  logger.debug("\n")
end
on_premise?() click to toggle source
# File lib/dynamics_crm/client.rb, line 301
def on_premise?
  @on_premise ||= !(hostname =~ /\.dynamics\.com/i)
end
organization_wsdl() click to toggle source
# File lib/dynamics_crm/client.rb, line 305
def organization_wsdl
  wsdl = open(organization_endpoint + "?wsdl=wsdl0").read
  @organization_wsdl ||= REXML::Document.new(wsdl)
end
post(url, request) click to toggle source
# File lib/dynamics_crm/client.rb, line 323
def post(url, request)
  log_xml('REQUEST', request)
  uri = URI.parse(url)

  http = Net::HTTP.new uri.host, uri.port
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER

  req = Net::HTTP::Post.new(uri.request_uri,
    'Connection' => 'Keep-Alive',
    'Content-type' => 'application/soap+xml; charset=UTF-8',
    'Content-length' => request.bytesize.to_s
  )
  req.body = request
  response = http.request(req)

  response_body = response.body
  log_xml('RESPONSE', response_body)

  response_body
end