Commit 2f11db4b authored by Michael Kozono's avatar Michael Kozono

Adapt DN class for Gitlab

parent 7bc4278e
# -*- ruby encoding: utf-8 -*- # -*- ruby encoding: utf-8 -*-
# Based on the `ruby-net-ldap` gem's `Net::LDAP::DN`
#
# For our purposes, this class is used to normalize DNs in order to allow proper
# comparison.
#
# E.g. DNs should be compared case-insensitively (in basically all LDAP
# implementations or setups), therefore we downcase every DN.
## ##
# Objects of this class represent an LDAP DN ("Distinguished Name"). A DN # Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
# ("Distinguished Name") is a unique identifier for an entry within an LDAP # ("Distinguished Name") is a unique identifier for an entry within an LDAP
...@@ -11,214 +19,218 @@ ...@@ -11,214 +19,218 @@
# #
# A fully escaped DN needs to be unescaped when analysing its contents. This # A fully escaped DN needs to be unescaped when analysing its contents. This
# class also helps take care of that. # class also helps take care of that.
class Net::LDAP::DN module Gitlab
## module LDAP
# Initialize a DN, escaping as required. Pass in attributes in name/value class DN
# pairs. If there is a left over argument, it will be appended to the dn ##
# without escaping (useful for a base string). # Initialize a DN, escaping as required. Pass in attributes in name/value
# # pairs. If there is a left over argument, it will be appended to the dn
# Most uses of this class will be to escape a DN, rather than to parse it, # without escaping (useful for a base string).
# so storing the dn as an escaped String and parsing parts as required #
# with a state machine seems sensible. # Most uses of this class will be to escape a DN, rather than to parse it,
def initialize(*args) # so storing the dn as an escaped String and parsing parts as required
buffer = StringIO.new # with a state machine seems sensible.
def initialize(*args)
buffer = StringIO.new
args.each_index do |index| args.each_index do |index|
buffer << "=" if index % 2 == 1 buffer << "=" if index % 2 == 1
buffer << "," if index % 2 == 0 && index != 0 buffer << "," if index % 2 == 0 && index != 0
if index < args.length - 1 || index % 2 == 1 if index < args.length - 1 || index % 2 == 1
buffer << Net::LDAP::DN.escape(args[index]) buffer << Net::LDAP::DN.escape(args[index])
else else
buffer << args[index] buffer << args[index]
end end
end end
@dn = buffer.string @dn = buffer.string
end end
## ##
# Parse a DN into key value pairs using ASN from # Parse a DN into key value pairs using ASN from
# http://tools.ietf.org/html/rfc2253 section 3. # http://tools.ietf.org/html/rfc2253 section 3.
def each_pair def each_pair
state = :key state = :key
key = StringIO.new key = StringIO.new
value = StringIO.new value = StringIO.new
hex_buffer = "" hex_buffer = ""
@dn.each_char do |char| @dn.each_char do |char|
case state case state
when :key then when :key then
case char case char
when 'a'..'z', 'A'..'Z' then when 'a'..'z', 'A'..'Z' then
state = :key_normal state = :key_normal
key << char key << char
when '0'..'9' then when '0'..'9' then
state = :key_oid state = :key_oid
key << char key << char
when ' ' then state = :key when ' ' then state = :key
else raise "DN badly formed" else raise "DN badly formed"
end end
when :key_normal then when :key_normal then
case char case char
when '=' then state = :value when '=' then state = :value
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
else raise "DN badly formed" else raise "DN badly formed"
end end
when :key_oid then when :key_oid then
case char case char
when '=' then state = :value when '=' then state = :value
when '0'..'9', '.', ' ' then key << char when '0'..'9', '.', ' ' then key << char
else raise "DN badly formed" else raise "DN badly formed"
end end
when :value then when :value then
case char case char
when '\\' then state = :value_normal_escape when '\\' then state = :value_normal_escape
when '"' then state = :value_quoted when '"' then state = :value_quoted
when ' ' then state = :value when ' ' then state = :value
when '#' then when '#' then
state = :value_hexstring state = :value_hexstring
value << char value << char
when ',' then when ',' then
state = :key state = :key
yield key.string.strip, value.string.rstrip yield key.string.strip, value.string.rstrip
key = StringIO.new key = StringIO.new
value = StringIO.new; value = StringIO.new;
else else
state = :value_normal state = :value_normal
value << char value << char
end end
when :value_normal then when :value_normal then
case char case char
when '\\' then state = :value_normal_escape when '\\' then state = :value_normal_escape
when ',' then when ',' then
state = :key state = :key
yield key.string.strip, value.string.rstrip yield key.string.strip, value.string.rstrip
key = StringIO.new key = StringIO.new
value = StringIO.new; value = StringIO.new;
else value << char else value << char
end end
when :value_normal_escape then when :value_normal_escape then
case char case char
when '0'..'9', 'a'..'f', 'A'..'F' then when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal_escape_hex state = :value_normal_escape_hex
hex_buffer = char hex_buffer = char
else state = :value_normal; value << char else state = :value_normal; value << char
end end
when :value_normal_escape_hex then when :value_normal_escape_hex then
case char case char
when '0'..'9', 'a'..'f', 'A'..'F' then when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_normal state = :value_normal
value << "#{hex_buffer}#{char}".to_i(16).chr value << "#{hex_buffer}#{char}".to_i(16).chr
else raise "DN badly formed" else raise "DN badly formed"
end end
when :value_quoted then when :value_quoted then
case char case char
when '\\' then state = :value_quoted_escape when '\\' then state = :value_quoted_escape
when '"' then state = :value_end when '"' then state = :value_end
else value << char else value << char
end end
when :value_quoted_escape then when :value_quoted_escape then
case char case char
when '0'..'9', 'a'..'f', 'A'..'F' then when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted_escape_hex state = :value_quoted_escape_hex
hex_buffer = char hex_buffer = char
else else
state = :value_quoted; state = :value_quoted;
value << char value << char
end
when :value_quoted_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise "DN badly formed"
end
when :value_hexstring then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring_hex
value << char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, value.string.rstrip
key = StringIO.new
value = StringIO.new;
else raise "DN badly formed"
end
when :value_hexstring_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring
value << char
else raise "DN badly formed"
end
when :value_end then
case char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, value.string.rstrip
key = StringIO.new
value = StringIO.new;
else raise "DN badly formed"
end
else raise "Fell out of state machine"
end
end end
when :value_quoted_escape_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_quoted
value << "#{hex_buffer}#{char}".to_i(16).chr
else raise "DN badly formed"
end
when :value_hexstring then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring_hex
value << char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, value.string.rstrip
key = StringIO.new
value = StringIO.new;
else raise "DN badly formed"
end
when :value_hexstring_hex then
case char
when '0'..'9', 'a'..'f', 'A'..'F' then
state = :value_hexstring
value << char
else raise "DN badly formed"
end
when :value_end then
case char
when ' ' then state = :value_end
when ',' then
state = :key
yield key.string.strip, value.string.rstrip
key = StringIO.new
value = StringIO.new;
else raise "DN badly formed"
end
else raise "Fell out of state machine"
end
end
# Last pair # Last pair
raise "DN badly formed" unless raise "DN badly formed" unless
[:value, :value_normal, :value_hexstring, :value_end].include? state [:value, :value_normal, :value_hexstring, :value_end].include? state
yield key.string.strip, value.string.rstrip yield key.string.strip, value.string.rstrip
end end
## ##
# Returns the DN as an array in the form expected by the constructor. # Returns the DN as an array in the form expected by the constructor.
def to_a def to_a
a = [] a = []
self.each_pair { |key, value| a << key << value } self.each_pair { |key, value| a << key << value }
a a
end end
## ##
# Return the DN as an escaped string. # Return the DN as an escaped string.
def to_s def to_s
@dn @dn
end end
# http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions # http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
# for dn values. All of the following must be escaped in any normal string # for dn values. All of the following must be escaped in any normal string
# using a single backslash ('\') as escape. # using a single backslash ('\') as escape.
ESCAPES = { ESCAPES = {
',' => ',', ',' => ',',
'+' => '+', '+' => '+',
'"' => '"', '"' => '"',
'\\' => '\\', '\\' => '\\',
'<' => '<', '<' => '<',
'>' => '>', '>' => '>',
';' => ';', ';' => ';',
} }
# Compiled character class regexp using the keys from the above hash, and # Compiled character class regexp using the keys from the above hash, and
# checking for a space or # at the start, or space at the end, of the # checking for a space or # at the start, or space at the end, of the
# string. # string.
ESCAPE_RE = Regexp.new("(^ |^#| $|[" + ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
ESCAPES.keys.map { |e| Regexp.escape(e) }.join + ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
"])") "])")
## ##
# Escape a string for use in a DN value # Escape a string for use in a DN value
def self.escape(string) def self.escape(string)
string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] } string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
end end
## ##
# Proxy all other requests to the string object, because a DN is mainly # Proxy all other requests to the string object, because a DN is mainly
# used within the library as a string # used within the library as a string
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
@dn.send(method, *args, &block) @dn.send(method, *args, &block)
end
end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment