class Vines::Stream::SASL

Provides plain (username/password) and external (TLS certificate) SASL authentication to client and server streams.

Constants

EMPTY

Public Class Methods

new(stream) click to toggle source
# File lib/vines/stream/sasl.rb, line 11
def initialize(stream)
  @stream = stream
end

Public Instance Methods

external_auth(encoded) click to toggle source

Authenticate server-to-server streams, comparing their domain to their SSL certificate.

http://xmpp.org/extensions/xep-0178.html#s2s

encoded - The Base64 encoded remote domain name String sent by the

server stream.

Returns true if the Base64 encoded domain matches the TLS certificate

presented earlier in stream negotiation.

Raises a SaslError if authentication failed.

# File lib/vines/stream/sasl.rb, line 27
def external_auth(encoded)
  unless encoded == EMPTY
    authzid = decode64(encoded)
    matches_from = (authzid == @stream.remote_domain)
    raise SaslErrors::InvalidAuthzid unless matches_from
  end
  matches_from = @stream.cert_domain_matches?(@stream.remote_domain)
  matches_from or raise SaslErrors::NotAuthorized
end
plain_auth(encoded) click to toggle source

Authenticate client-to-server streams using a username and password.

encoded - The Base64 encoded jid and password String sent by the

client stream.

Returns the authenticated User or raises SaslError if authentication failed.

# File lib/vines/stream/sasl.rb, line 43
def plain_auth(encoded)
  jid, password = decode_credentials(encoded)
  user = authenticate(jid, password)
  user or raise SaslErrors::NotAuthorized
end

Private Instance Methods

authenticate(jid, password) click to toggle source

Storage backends should not raise errors, but if an unexpected error occurs during authentication, convert it to a temporary-auth-failure.

jid - The user's jid String. password - The String password.

Returns the authenticated User or nil if authentication failed.

Raises TemoraryAuthFailure if the storage system failed.

# File lib/vines/stream/sasl.rb, line 60
def authenticate(jid, password)
  log.info("Authenticating user: %s" % jid)
  @stream.storage.authenticate(jid, password).tap do |user|
    log.info("Authentication succeeded: %s" % user.jid) if user
  end
rescue => e
  log.error("Failed to authenticate: #{e.to_s}")
  raise SaslErrors::TemporaryAuthFailure
end
decode64(encoded) click to toggle source

Decode the Base64 encoded string, raising an error for invalid data.

http://tools.ietf.org/html/rfc6120#section-13.9.1

encoded - The Base64 encoded String.

Returns a UTF-8 String.

# File lib/vines/stream/sasl.rb, line 118
def decode64(encoded)
  Base64.strict_decode64(encoded).tap do |decoded|
    decoded.force_encoding(Encoding::UTF_8)
    raise SaslErrors::IncorrectEncoding unless decoded.valid_encoding?
  end
rescue
  raise SaslErrors::IncorrectEncoding
end
decode_credentials(encoded) click to toggle source

Return the JID and password decoded from the Base64 encoded SASL PLAIN credentials formatted as authzid0authcid0password.

http://tools.ietf.org/html/rfc6120#section-6.3.8
http://tools.ietf.org/html/rfc4616

encoded - The Base64 encoded String from which to extract jid and password.

Returns an Array of jid String and password String.

# File lib/vines/stream/sasl.rb, line 79
def decode_credentials(encoded)
  authzid, node, password = decode64(encoded).split("\x00")
  raise SaslErrors::NotAuthorized if node.nil? || node.empty? || password.nil? || password.empty?
  jid = JID.new(node, @stream.domain) rescue (raise SaslErrors::NotAuthorized)
  validate_authzid!(authzid, jid)
  [jid, password]
end
validate_authzid!(authzid, jid) click to toggle source

An optional SASL authzid allows a user to authenticate with one user name and password and then have their connection authorized as a different ID (the authzid). We don't support that, so raise an error if the authzid is provided and different than the authcid.

Most clients don't send an authzid at all because it's optional and not widely supported. However, Strophe and Blather send a bare JID, in compliance with RFC 6120, but Smack sends just the user name as the authzid. So, take care to handle non-compliant clients here.

http://tools.ietf.org/html/rfc6120#section-6.3.8

authzid - The authzid String (may be nil). jid - The username String.

Returns nothing.

# File lib/vines/stream/sasl.rb, line 103
def validate_authzid!(authzid, jid)
  return if authzid.nil? || authzid.empty?
  authzid.downcase!
  smack = authzid == jid.node
  compliant = authzid == jid.to_s
  raise SaslErrors::InvalidAuthzid unless compliant || smack
end