class WiKID::Auth
Constants
- VERSION
Public Class Methods
This constructor allows the Auth_WiKID module to be initialized from either a properties file or via explicit arguments.
@param [string] host_or_file Either the IP address or hostname of
the wAuth server, or the path to a properties file
@param [int] port The SSL listener port for the wAuth
daemon on the wAuth server
@param [string] keyfile The PKCS12 keystore generated for this
client by the wAuth server
@param [string] keypass The passphrase securing the keys in keyfile @param [string] cafile The certificate authority store for
validating the wAuth server certificate
The contents of the properties file should contain the following key-value pairs:
-
host - The IP address or hostname of the wAuth server
-
port - The SSL listener port for the wAuth daemon on the wAuth server
-
keyfile - The PKCS12 keystore generated for client by the wAuth server
-
pass - The passphrase securing the keys in keyfile
-
cafile - The PEM-encoded certificate file for validating the wAuth server certificate
end¶ ↑
# File lib/WiKID.rb, line 110 def initialize(host_or_file, port, keyfile, keypass, cafile = @@DEFAULT_CA_FILE) unless SSLEnabled raise RuntimeError.new('Ruby/OpenSSL module is required for WiKID authentication.') end if (File.exist?(host_or_file)) # props = parse_ini_file(host_or_file) props = Hash.new @host = props['host'] @port = props['port'] @keyfile = props['keyfile'] @keypass = props['pass'] @cafile = props['cafile'] else @host = host_or_file.untaint @port = port.untaint @keyfile = keyfile.untaint @keypass = keypass.untaint unless cafile.nil? || cafile.empty? @cafile = cafile.untaint end end if (!@port.is_a?(Integer)) @port = 0 end _dprint("WiKID.rb initialized: host=#{@host}, port=#{@port}, keyfile=#{@keyfile}, cafile=#{@cafile}") ## simple hack to allow for testing during gem installation (prevents security errors since keys may not yet be available) unless port == -1 checkKeys() end return true end
Public Instance Methods
Verifies the credentials via challenge-response.
@note Not currently supported by the Open Source release of WiKID
.
@return [boolean] 'true' indicates credentials were valid,
'false' if credentials were invalid or an error occurred
end¶ ↑
# File lib/WiKID.rb, line 522 def chapVerify(username, domaincode, wikidChallenge = '', chapPassword = '', chapChallenge = '') _dprint('chapVerify() called ...') reconnect() validCredentials = false valid_tag = 'VERIFY:VALID' _dprint('Checking Chap Credentials') mesg = "CHAPOFFVERIFY:" + username + "\t" + "nil" + "\t" + domaincode + "\t" + wikidChallenge reconnect { $sslsocket.puts(chapPassword.length) $sslsocket.puts(chapPassword) $sslsocket.puts(chapChallenge.length) $sslsocket.puts(chapChallenge.length) $sslsocket.flush _dprint("Reading in...") inputLine = $sslsocket.gets.chomp if (inputLine[0, valid_tag.length] == valid_tag) validCredentials = true end } return validCredentials end
Verifies the credentials that are generated using the standard authentication method.
@param [string] username Users login ID in this authentication domain @param [string] passcode Passcode provided by the user @param [string] domaincode 12 digit code representing the
authentication domain
@return [boolean] 'true' indicates credentials were valid,
'false' if credentials were invalid or an error occurred
end¶ ↑
# File lib/WiKID.rb, line 464 def checkCredentials(username, passcode, domaincode = '127000000001') _dprint("checkCredentials(#{username}, #{passcode}, #{domaincode}) called ...") validCredentials = false offline_challenge = '' offline_response = '' chap_password = '' chap_challenge = '' valid_tag = 'VERIFY:VALID' _dprint('Checking Credentials...') mesg = "VERIFY:" + username + "\t" + passcode + "\t" + domaincode mesg = <<XML <transaction> <type format="base">2</type> <data> <user-id>#{username}</user-id> <passcode>#{passcode}</passcode> <domaincode>#{domaincode}</domaincode> <offline-challenge encoding="none">#{offline_challenge}</offline-challenge> <offline-response encoding="none">#{offline_response}</offline-response> <chap-password encoding="none">#{chap_password}</chap-password> <chap-challenge encoding="none">#{chap_challenge}</chap-challenge> <result>null</result> </data> </transaction> XML reconnect { xml = _request(mesg) response = XPath.first(xml, '//data/result') if response =~ /VALID/ validCredentials = true else validCredentials = false end _dprint('Read response: verdict = ' + validCredentials.to_s) } _dprint('Returning Results...') return validCredentials end
This method checks that the certificates are readable and accessible.
end¶ ↑
# File lib/WiKID.rb, line 179 def checkKeys() data = nil if (@cafile.nil? || @cafile.empty? || !File.exists?(@cafile) || OpenSSL::X509::Certificate.new(File.read(@cafile)).nil?) warn 'CA certificate NOT OK, running without peer verification' else _dprint('CA certificate OK') end if (@keyfile.nil? || @keyfile.empty? || !File.exists?(@keyfile) || OpenSSL::X509::Certificate.new(File.read(@keyfile)).nil?) raise SecurityError, 'Public key NOT OK!' else _dprint('Public key OK') end if (!File.exists?(@keyfile) || OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass).nil?) raise SecurityError, 'Private key NOT OK!' else _dprint('Private key OK') end end
This method simply closes the connection to the wAuth service.
end¶ ↑
# File lib/WiKID.rb, line 160 def close() _dprint('Closing Auth_WiKID connection ...') unless $sslsocket.nil? unless $sslsocket.closed? $sslsocket.puts('QUIT'); $sslsocket.flush $sslsocket.close end $sslsocket = nil @socket.shutdown end @isConnected = false end
Delete a user by userid
@param [string] user_id The userid on the server, or a user object as returned
by a call to findUser()
@param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary
if first argument is the user object, which will already have this
@return [boolean] ' true ' indicates deletion was successful
end¶ ↑
# File lib/WiKID.rb, line 720 def deleteUser(user_id, domaincode = ' 127000000001 ') successful = false _dprint("deleteUser(#{user_id},#{domaincode})") if (user_id.is_a?(String)) user_xml = findUser(user_id, domaincode) end if user_xml.nil? return false end _dprint("user: #{user_id}") mesg = <<XML <transaction> <type>7</type> <data> <domaincode>#{domaincode}</domaincode> <user-id>#{user_id}</user-id> #{user_xml} <result>null</result> <return-code>-2147483648</return-code> </data> </transaction> XML reconnect { _dprint('deleting user ...') xml = _request(mesg) unless xml.nil? response = XPath.first(xml, '//data/result') _dprint("response: '#{response}'") if response.to_s =~ /SUCC?ESS/ successful = true else successful = false end end return successful } end
Find a user by username
@param [string] username the textual id of the user to search for @param [string] domaincode the 12 digit code representating the authentication domain
@return [String] The XML representing the user object, if successful; nil if unsuccesful
end¶ ↑
# File lib/WiKID.rb, line 615 def findUser(username, domaincode = '127000000001') _dprint("findUser() ..."); user = nil mesg = <<XML <transaction> <type>5</type> <data> <domaincode>#{domaincode}</domaincode> <user-id>#{username}</user-id> <result>null</result> <return-code>-2147483648</return-code> </data> </transaction> XML reconnect { _dprint("Looking up user ..."); xml = _request(mesg) user_xml = XPath.first(xml, '//data/user') return user_xml } end
Fetches a list of domains served by the currently connected server code.
@note Not currently supported by the Open Source release of WiKID
.
@return [string] ' true ' indicates credentials were valid,
' false ' if credentials were invalid or an error occurred
end¶ ↑
# File lib/WiKID.rb, line 784 def getDomains() _dprint("getDomains() called ...") valid_tag = "DOMAINLIST" _dprint("Getting Domains") mesg = <<XML <transaction> <type>3</type> <data> <domain-list>null</domain-list> </data> </transaction> XML reconnect { xml = _request(mesg) domains = XPath.match(xml, '//data/domain-list') } _dprint("Returning Results...") return domains end
Send a big ping transaction to the server to verify the connection is good.
@note This method should not be necessary in typical implementations, but is available nonetheless.
@return [string] the raw response from the server
end¶ ↑
# File lib/WiKID.rb, line 249 def ping() mesg = '<transaction> <type>1</type> <data> <value>TX</value> </data> </transaction>' xml = _request(mesg) return xml end
This method supports user pre-registration. You may upload a list of userids and pre-registration codes into the server via the WiKIDAdmin interface. Users can then use the pre-registration code provided to them securely by the administrator in conjunction with the registration code provided by the WiKID
token to register in an expedited manner.
@param [string] tokenCode the registration code provided by the token @param [string] preRegCode the code associated with the username that was uploaded to the server @param [string] domaincode 12 digit code representing the authentication server/domain
@return [boolean] 'true' indicates that the pre-registration was successful;
'false' if not
end¶ ↑
# File lib/WiKID.rb, line 567 def preRegister(tokenCode, preRegCode, domaincode = '127000000001') _dprint('preRegister() called ...') successful = false; mesg = <<XML <transaction> <type>10</type> <data> <token-registration-code>#{tokenCode}</token-registration-code> <pre-registration-code>#{preRegCode}</pre-registration-code> <domaincode>#{domaincode}</domaincode> <error-code>null</error-code> <result>null</result> </data> </transaction> XML reconnect { _dprint('Pre-registering ...') xml = _request(mesg) response = XPath.first(xml, '//data/result') _dprint("response: '#{response}'") if response.to_s =~ /SUC?CESS/ successful = true else successful = false end } _dprint("Read response: verdict = #{successful}") return successful end
This method reconnects to the wAuth server, if the socket handle is dead.
@return [boolean] Whether the socket is connected
end¶ ↑
# File lib/WiKID.rb, line 285 def reconnect() _dprint("reconnect() called.") begin if ($sslsocket.nil? || $sslsocket.closed?) _dprint("Socket inactive. Reconnecting...") #puts "Setting up SSL context ..." ctx = OpenSSL::SSL::SSLContext.new() # Options: # "cert", "key", "client_ca", "ca_file", "ca_path", # "timeout", "verify_mode", "verify_depth", # "verify_callback", "options", "cert_store", "extra_chain_cert" ctx.cert = OpenSSL::X509::Certificate.new(File.read(@keyfile)) ctx.key = OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass) if @cafile.nil? || @cafile.empty? ctx.ca_file = nil # @cafile ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE else ctx.ca_file = @cafile # this next bit might be redundant? ctx.cert_store = OpenSSL::X509::Store.new ctx.cert_store.set_default_paths ctx.cert_store.add_file(@cafile) ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT end # ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE ctx.timeout = @@timeout if ctx.cert.nil? _dprint("warning: peer certificate won't be verified this session.") ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE end _dprint("Opening socket to #{@host}:#{@port}...") @socket = TCPSocket.open(@host, @port) _dprint("socket open") #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, @@timeout) #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, @@timeout) $sslsocket = OpenSSL::SSL::SSLSocket.new(@socket, ctx) _dprint("socket created") #$sslsocket.sync_close = true # $sslsocket should be good now _dprint("Connecting SSL socket ...") $sslsocket.connect _startConnection() end if block_given? #puts "Connecting SSL socket in block ..." $sslsocket.connect if $sslsocket.closed? yield #puts "SSL connection block finished." else #puts "SSL connection wanting to do something else ..." # do something non-OO end rescue Exception => ex warn "Error reading from server: #{ex}" end end
Creates an association between the userid and the device registered by the user.
@param [string] username Users login ID in this authentication domain @param [string] regcode Registration code provided to user when
setting up this domain on users device
@param [string] domaincode 12 digit code representing this
authentication domain
@param [string] passcode Optional passcode provided by the user, to
link this device to an existing registration
@return [int] Result code from the registration attempt
end¶ ↑
# File lib/WiKID.rb, line 390 def registerUsername(username, regcode, domaincode, groupname = '', passcode = '') _dprint('registerUsername() called ...') valid_tag = 'REGUSER:SUCESS' if (!passcode.nil? && passcode.length > 0) _dprint('Adding new device ...') command = 'ADDREGUSER' type = 4; passcodeline = "<passcode>#{passcode}</passcode>"; format = 'add'; else _dprint('Registering user ...') command = 'REGUSER' type = 4; passcodeline = '<passcode>null</passcode>'; format = 'new'; end if (!groupname.nil? && groupname.length>0) groupnameline="<groupName>#{groupname}</groupName>" else groupnameline='<groupName>null</groupName>' end #mesg = "#{command}:#{username}\t#{regcode}\t#{domaincode}\t#{passcode}" mesg = <<XML <transaction> <type format="#{format}">#{type}</type> <data> <user-id>#{username}</user-id> <registration-code>#{regcode}</registration-code> <domaincode>#{domaincode}</domaincode> #{passcodeline} #{groupnameline} <error-code>null</error-code> <result>null</result> </data> </transaction> XML #puts mesg reconnect { _dprint("registerUsername() sending '#{mesg}' ...") xml = _request(mesg) response = XPath.first(xml, '//data/result') _dprint("response: '#{response}'") if response.to_s =~ /SUCC?ESS/ _dprint('Registered!') return 0 else err = XPath.first(xml, '//data/error-code/text()') _dprint("Failed to register! Error: #{err}") return err end } end
Update the previously “found” user
This method is used to update a user object on the server. The network client certificate that was used to establish the wClient connection must be authorized to perform this action.
@param [string] user_id The userid on the server, or a user object as returned
by a call to findUser()
@param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary
if first argument is the user object, which will already have this
@return [boolean] 'true' indicates the update was successful
end¶ ↑
# File lib/WiKID.rb, line 659 def updateUser(user_id, domaincode = '127000000001', updateUserXml = '') successful = false _dprint("updateUser(#{user_id},#{domaincode})") if (user_id.is_a?(String)) user_xml = findUser(user_id, domaincode) end if user_xml.nil? return false end _dprint("user_xml: #{user_xml}, updating to #{updateUserXml}") mesg = <<XML <transaction> <type>6</type> <data> <domaincode>#{domaincode}</domaincode> <user-id>#{user_id}</user-id> #{updateUserXml} <result>null</result> <return-code>-2147483648</return-code> </data> </transaction> XML reconnect { _dprint('updating user ...') xml = _request(mesg) response = XPath.first(xml, '//data/result') _dprint("response: '#{response}'") if response.to_s =~ /SUCC?ESS/ successful = true else successful = false end } return successful end
Private Instance Methods
@note Class destructor, which just calls close(). @api private
# File lib/WiKID.rb, line 150 def _WiKID() close() end
Prints a message if the debug flag is true, time-stamped since the epoch.
@param [string] msg Message to print out @api private @private
end¶ ↑
# File lib/WiKID.rb, line 825 def _dprint(msg) if (@@DEBUG) show = Time.now.to_s + ' : ' + msg show += '<br />' if !ENV['REQUEST_URI'].nil? puts show #STDERR.puts show #STDERR.flush() end return true end
Send the request and get the response back from the server.
@param [string] mesg The message to send to the server
@return [string] The response from the server
@api private @private _request
end¶ ↑
# File lib/WiKID.rb, line 214 def _request(mesg) mesg.gsub!(/\n/, '') _dprint("send.request is: #{mesg.inspect}") #puts "---------------------------------" $sslsocket.puts(mesg) $sslsocket.flush _dprint("checking response...") raw_response = $sslsocket.gets _dprint("send.raw_response is: #{raw_response.inspect}") response = raw_response.chomp unless raw_response.nil? _dprint("send.response is: #{response.inspect}") unless response.nil? #puts "creating xml" xml = Document.new response #puts xml.inspect else #puts 'No response received.' xml = nil end #puts "returning XML" return xml end
This method initiates the connection to the wAuth server.
@return [boolean] Whether the socket is connected @api private
end¶ ↑
# File lib/WiKID.rb, line 262 def _startConnection() _dprint("startConnection() called.") valid_tag = "ACCEPT"; # The client initiates the transaction mesg = "CONNECT: WiKID Ruby Client v#{VERSION}" mesg = "<transaction> <type>1</type> <data> <client-string>wClient Ruby #{VERSION}</client-string> <server-string>null</server-string> <result>null</result> </data> </transaction> " xml = _request(mesg); result = XPath.first(xml, '//data/result') @isConnected = (result == 'ACCEPT') return @isConnected end