Class: RbVmomi::SSO

Inherits:
Object
  • Object
show all
Defined in:
lib/rbvmomi/sso.rb

Overview

Provides access to vCenter Single Sign-On

Constant Summary collapse

BST_PROFILE =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'.freeze
C14N_CLASS =
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
C14N_METHOD =
'http://www.w3.org/2001/10/xml-exc-c14n#'.freeze
DIGEST_METHOD =
'http://www.w3.org/2001/04/xmlenc#sha512'.freeze
ENCODING_METHOD =
'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'.freeze
SIGNATURE_METHOD =
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'.freeze
STS_PATH =
'/sts/STSService'.freeze
TOKEN_TYPE =
'urn:oasis:names:tc:SAML:2.0:assertion'.freeze
TOKEN_PROFILE =
'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0'.freeze
NAMESPACES =
{
  :ds => 'http://www.w3.org/2000/09/xmldsig#',
  :soap => 'http://schemas.xmlsoap.org/soap/envelope/',
  :wsse => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
  :wsse11 => 'http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd',
  :wst => 'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
  :wsu => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ SSO

Creates an instance of an SSO object

Parameters:

  • opts (Hash) (defaults to: {})

    the options to create the object with

Options Hash (opts):

  • :host (String)

    the host to connect to

  • :port (Fixnum) — default: 443

    the port to connect to

  • :path (String)

    the path to call

  • :user (String)

    the user to authenticate with

  • :password (String)

    the password to authenticate with

  • :private_key (String)

    the private key to use

  • :certificate (String)

    the certificate to use

  • :insecure (Boolean) — default: false

    whether to connect insecurely



50
51
52
53
54
55
56
57
58
59
# File 'lib/rbvmomi/sso.rb', line 50

def initialize(opts = {})
  @host     = opts[:host]
  @insecure = opts.fetch(:insecure, false)
  @password = opts[:password]
  @path     = opts.fetch(:path, STS_PATH)
  @port     = opts.fetch(:port, 443)
  @user     = opts[:user]

  load_x509(opts[:private_key], opts[:certificate])
end

Instance Attribute Details

#assertionObject (readonly)

Returns the value of attribute assertion.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def assertion
  @assertion
end

#assertion_idObject (readonly)

Returns the value of attribute assertion_id.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def assertion_id
  @assertion_id
end

#certificateObject (readonly)

Returns the value of attribute certificate.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def certificate
  @certificate
end

#hostObject (readonly)

Returns the value of attribute host.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def host
  @host
end

#passwordObject (readonly)

Returns the value of attribute password.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def password
  @password
end

#pathObject (readonly)

Returns the value of attribute path.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def path
  @path
end

#portObject (readonly)

Returns the value of attribute port.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def port
  @port
end

#private_keyObject (readonly)

Returns the value of attribute private_key.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def private_key
  @private_key
end

#userObject (readonly)

Returns the value of attribute user.



29
30
31
# File 'lib/rbvmomi/sso.rb', line 29

def user
  @user
end

Instance Method Details

#request_tokenObject



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/rbvmomi/sso.rb', line 61

def request_token
  req = sso_call(hok_token_request)

  unless req.is_a?(Net::HTTPSuccess)
    resp = Nokogiri::XML(req.body)
    resp.remove_namespaces!
    raise(resp.at_xpath('//Envelope/Body/Fault/faultstring/text()'))
  end

  extract_assertion(req.body)
end

#sign_request(request) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rbvmomi/sso.rb', line 73

def sign_request(request)
  raise('Need SAML2 assertion') unless @assertion
  raise('No SAML2 assertion ID') unless @assertion_id

  request_id = generate_id
  timestamp_id = generate_id

  request = request.is_a?(String) ? Nokogiri::XML(request) : request
  builder = Nokogiri::XML::Builder.new do |xml|
    xml[:soap].Header(Hash[NAMESPACES.map { |ns, uri| ["xmlns:#{ns}", uri] }]) do
      xml[:wsse].Security do
        wsu_timestamp(xml, timestamp_id)
        ds_signature(xml, request_id, timestamp_id) do |x|
          x[:wsse].SecurityTokenReference('wsse11:TokenType' => TOKEN_PROFILE) do
            x[:wsse].KeyIdentifier(
              @assertion_id,
              'ValueType' => 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID'
            )
          end
        end
      end
    end
  end

  # To avoid Nokogiri mangling the token, we replace it as a string
  # later on. Figure out a way around this.
  builder.doc.at_xpath('//soap:Header/wsse:Security/wsu:Timestamp').add_previous_sibling(Nokogiri::XML::Text.new('SAML_ASSERTION_PLACEHOLDER', builder.doc))

  request.at_xpath('//soap:Envelope', NAMESPACES).tap do |e|
    NAMESPACES.each do |ns, uri|
      e.add_namespace(ns.to_s, uri)
    end
  end
  request.xpath('//soap:Envelope/soap:Body').each do |body|
    body.add_previous_sibling(builder.doc.root)
    body.add_namespace('wsu', NAMESPACES[:wsu])
    body['wsu:Id'] = request_id
  end

  signed = sign(request)
  signed.gsub!('SAML_ASSERTION_PLACEHOLDER', @assertion.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).strip)

  signed
end

#sso_call(body) ⇒ Object

We default to Issue, since that’s all we currently need.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/rbvmomi/sso.rb', line 119

def sso_call(body)
  sso_url = URI::HTTPS.build(:host => @host, :port => @port, :path => @path)
  http = Net::HTTP.new(sso_url.host, sso_url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @insecure

  req = Net::HTTP::Post.new(sso_url.request_uri)
  req.add_field('Accept', 'text/xml, multipart/related')
  req.add_field('User-Agent', "VMware/RbVmomi #{RbVmomi::VERSION}")
  req.add_field('SOAPAction', 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue')
  req.content_type = 'text/xml; charset="UTF-8"'
  req.body = body

  http.request(req)
end