require 'auth/middleware' require 'hws_settings' require 'p4_error' require 'pathname' require 'P4' require 'json' require 'pp' # This is mostly a namespace of Authentication-related methods. module Auth # Returns true if the string looks like a Perforce authentication ticket. def self.p4_ticket?(str) /^[a-zA-Z0-9]{32,}$/.match(str) != nil end def self.read_token(token, settings) token_path = Pathname.new(settings.token_path) + token if File.exist?(token_path) File.open(token_path, 'r') do |file| return JSON.parse(file.read) end end nil end private def self.warn_if_tmp_dir(token_dir) if token_dir.start_with?('/tmp') puts "Your token directory is using the default '/tmp' location, "\ 'please reconfigure to a reliable location' end end def self.warn_illegal_privileges(mode, token_dir) warn_unless_user_rwx(mode, token_dir) warn_no_group_write(mode, token_dir) warn_no_group_read(mode, token_dir) warn_no_group_execute(mode, token_dir) warn_no_other_write(mode, token_dir) warn_no_other_read(mode, token_dir) warn_no_other_execute(mode, token_dir) end # Check owner read/write/execute - should all be there def self.warn_unless_user_rwx(mode, token_dir) unless (mode & 0400 == 0400) && (mode & 0200 == 0200) && (mode & 0100 == 0100) puts "The token_path '#{token_dir}' should allow the owner read, "\ 'write and execute privileges' end end def self.warn_no_group_write(mode, token_dir) if (mode & 0040 == 0040) puts "The token_path '#{token_dir}' should not have group write access" end end def self.warn_no_group_read(mode, token_dir) if (mode & 0020 == 0020) puts "The token_path '#{token_dir}' should not have group read access" end end def self.warn_no_group_execute(mode, token_dir) if (mode & 0010 == 0010) puts "The token_path '#{token_dir}' should not have group "\ 'execute access' end end def self.warn_no_other_write(mode, token_dir) if (mode & 0004 == 0004) puts "The token_path '#{token_dir}' should not have other write access" end end def self.warn_no_other_read(mode, token_dir) if (mode & 0002 == 0002) puts "The token_path '#{token_dir}' should not have other read access" end end def self.warn_no_other_execute(mode, token_dir) if (mode & 0001 == 0001) puts "The token_path '#{token_dir}' should not have other "\ 'execute access' end end # We want special error handling here to return 4xx codes instead of 5xx # in the face of an invalid password. Temporarily drop to lowest # exception level, and just return nil when login doesn't work. # # We also have special handling here to take care of setting up p4trust. def self.ticket_from_login(p4) results = nil p4.at_exception_level(P4::RAISE_NONE) do results = p4.run_login('-p', '-a') end # Look for trust failure. # # If a TRUSTED_FINGERPRINTS file is set, then trust it only if fingerprint # is in the file. If it's not in the file, we do not trust. # # Otherwise, if ENABLE_MAN_IN_MIDDLE_ATTACKS is true, we run 'p4 trust -f'. # This should generally be disabled by default, and only enabled for # test servers. if p4.messages && p4.messages.any? { |m| m.msgid == 3120 || m.msgid == 3121 } if !HWSSettings.system.TRUST_FINGERPRINTS.nil? fprint = load_fingerprint(p4.messages.first { |m| m.msgid == 3120 || m.msgid == 3121 }) trusted = load_trust_fingerprints if trusted.include?(fprint) p4.run_trust('-f', '-i', fprint) end elsif HWSSettings.system.ENABLE_MAN_IN_MIDDLE_ATTACKS == true # This will throw exceptions in cases where the trust actually succeeded p4.at_exception_level(P4::RAISE_NONE) do trust_results = p4.run_trust('-y', '-f') puts "p4 trust -y -f:" pp trust_results end else raise_error(p4) end p4.at_exception_level(P4::RAISE_NONE) do results = p4.run_login('-p', '-a') puts "p4 login -p -a:" pp results end end auth_ok = raise_unless_auth_error(p4) if !auth_ok nil else # In security 0 servers, if the user does not need to log in, return an # empty string as our valid ticket value. if p4.messages && !p4.messages.empty? && p4.messages.first.msgid == 7481 p4.password = '' else p4.password = results[0] end end end def self.load_trust_fingerprints IO.read(HWSSettings.system.TRUST_FINGERPRINTS).split(/\n/) end # Yep. I can not figure out any other information sent by the server that # provides the fingerprint that we need to validate. def self.load_fingerprint(message) re = /The fingerprint for the key sent to your client is\n(.*)\nTo allow connection use the 'p4 trust' command./ re.match(message.to_s)[1] end def self.raise_unless_auth_error(p4) if Auth.error?(p4) msg = p4.messages[0] if msg.msgid == 7205 || # invalid user msg.msgid == 7206 # invalid password return false else Auth.raise_error(p4) end end true end def self.user_info(p4, results, ticket) { user: p4.user, email: results[0]['Email'], full_name: results[0]['FullName'], ticket: ticket } end # Check for P4 errors def self.error?(p4) !p4.errors.empty? end # Raise an exception if necessary on a P4 Error def self.raise_error(p4) err = p4.messages.find { |m| m.severity > 2 } fail P4Error.new(err.msgid, err.severity, err.to_s) end end