Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
e610332e
Commit
e610332e
authored
Sep 21, 2017
by
Michael Kozono
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Normalize existing persisted DNs
parent
14ed20d6
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
285 additions
and
0 deletions
+285
-0
db/post_migrate/20170921101004_normalize_ldap_extern_uids.rb
db/post_migrate/20170921101004_normalize_ldap_extern_uids.rb
+285
-0
No files found.
db/post_migrate/20170921101004_normalize_ldap_extern_uids.rb
0 → 100644
View file @
e610332e
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class
NormalizeLdapExternUids
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
class
Identity
<
ActiveRecord
::
Base
self
.
table_name
=
'identities'
end
# Copied this class to make this migration resilient to future code changes.
# And if the normalize behavior is changed in the future, it must be
# accompanied by another migration.
module
Gitlab
module
LDAP
MalformedDnError
=
Class
.
new
(
StandardError
)
UnsupportedDnFormatError
=
Class
.
new
(
StandardError
)
class
DN
##
# 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
# without escaping (useful for a base string).
#
# Most uses of this class will be to escape a DN, rather than to parse it,
# so storing the dn as an escaped String and parsing parts as required
# with a state machine seems sensible.
def
initialize
(
*
args
)
buffer
=
StringIO
.
new
args
.
each_index
do
|
index
|
buffer
<<
"="
if
index
.
odd?
buffer
<<
","
if
index
.
even?
&&
index
!=
0
arg
=
args
[
index
].
downcase
buffer
<<
if
index
<
args
.
length
-
1
||
index
.
odd?
self
.
class
.
escape
(
arg
)
else
arg
end
end
@dn
=
buffer
.
string
end
##
# Parse a DN into key value pairs using ASN from
# http://tools.ietf.org/html/rfc2253 section 3.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def
each_pair
state
=
:key
key
=
StringIO
.
new
value
=
StringIO
.
new
hex_buffer
=
""
@dn
.
each_char
do
|
char
|
case
state
when
:key
then
case
char
when
'a'
..
'z'
then
state
=
:key_normal
key
<<
char
when
'0'
..
'9'
then
state
=
:key_oid
key
<<
char
when
' '
then
state
=
:key
else
raise
(
MalformedDnError
,
"Unrecognized first character of an RDN attribute type name
\"
#{
char
}
\"
"
)
end
when
:key_normal
then
case
char
when
'='
then
state
=
:value
when
'a'
..
'z'
,
'0'
..
'9'
,
'-'
,
' '
then
key
<<
char
else
raise
(
MalformedDnError
,
"Unrecognized RDN attribute type name character
\"
#{
char
}
\"
"
)
end
when
:key_oid
then
case
char
when
'='
then
state
=
:value
when
'0'
..
'9'
,
'.'
,
' '
then
key
<<
char
else
raise
(
MalformedDnError
,
"Unrecognized RDN OID attribute type name character
\"
#{
char
}
\"
"
)
end
when
:value
then
case
char
when
'\\'
then
state
=
:value_normal_escape
when
'"'
then
state
=
:value_quoted
when
' '
then
state
=
:value
when
'#'
then
state
=
:value_hexstring
value
<<
char
when
','
then
state
=
:key
yield
key
.
string
.
strip
,
value
.
string
.
rstrip
key
=
StringIO
.
new
value
=
StringIO
.
new
else
state
=
:value_normal
value
<<
char
end
when
:value_normal
then
case
char
when
'\\'
then
state
=
:value_normal_escape
when
','
then
state
=
:key
yield
key
.
string
.
strip
,
value
.
string
.
rstrip
key
=
StringIO
.
new
value
=
StringIO
.
new
when
'+'
then
raise
(
UnsupportedDnFormatError
,
"Multivalued RDNs are not supported"
)
else
value
<<
char
end
when
:value_normal_escape
then
case
char
when
'0'
..
'9'
,
'a'
..
'f'
then
state
=
:value_normal_escape_hex
hex_buffer
=
char
when
/\s/
then
state
=
:value_normal_escape_whitespace
value
<<
char
else
state
=
:value_normal
value
<<
char
end
when
:value_normal_escape_hex
then
case
char
when
'0'
..
'9'
,
'a'
..
'f'
then
state
=
:value_normal
value
<<
"
#{
hex_buffer
}#{
char
}
"
.
to_i
(
16
).
chr
else
raise
(
MalformedDnError
,
"Invalid escaped hex code
\"\\
#{
hex_buffer
}#{
char
}
\"
"
)
end
when
:value_normal_escape_whitespace
then
case
char
when
'\\'
then
state
=
:value_normal_escape
when
','
then
state
=
:key
yield
key
.
string
.
strip
,
value
.
string
# Don't strip trailing escaped space!
key
=
StringIO
.
new
value
=
StringIO
.
new
when
'+'
then
raise
(
UnsupportedDnFormatError
,
"Multivalued RDNs are not supported"
)
else
value
<<
char
end
when
:value_quoted
then
case
char
when
'\\'
then
state
=
:value_quoted_escape
when
'"'
then
state
=
:value_end
else
value
<<
char
end
when
:value_quoted_escape
then
case
char
when
'0'
..
'9'
,
'a'
..
'f'
then
state
=
:value_quoted_escape_hex
hex_buffer
=
char
else
state
=
:value_quoted
value
<<
char
end
when
:value_quoted_escape_hex
then
case
char
when
'0'
..
'9'
,
'a'
..
'f'
then
state
=
:value_quoted
value
<<
"
#{
hex_buffer
}#{
char
}
"
.
to_i
(
16
).
chr
else
raise
(
MalformedDnError
,
"Expected the second character of a hex pair inside a double quoted value, but got
\"
#{
char
}
\"
"
)
end
when
:value_hexstring
then
case
char
when
'0'
..
'9'
,
'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
(
MalformedDnError
,
"Expected the first character of a hex pair, but got
\"
#{
char
}
\"
"
)
end
when
:value_hexstring_hex
then
case
char
when
'0'
..
'9'
,
'a'
..
'f'
then
state
=
:value_hexstring
value
<<
char
else
raise
(
MalformedDnError
,
"Expected the second character of a hex pair, but got
\"
#{
char
}
\"
"
)
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
(
MalformedDnError
,
"Expected the end of an attribute value, but got
\"
#{
char
}
\"
"
)
end
else
raise
"Fell out of state machine"
end
end
# Last pair
raise
(
MalformedDnError
,
'DN string ended unexpectedly'
)
unless
[
:value
,
:value_normal
,
:value_hexstring
,
:value_end
].
include?
state
yield
key
.
string
.
strip
,
value
.
string
.
rstrip
end
##
# Returns the DN as an array in the form expected by the constructor.
def
to_a
a
=
[]
self
.
each_pair
{
|
key
,
value
|
a
<<
key
<<
value
}
unless
@dn
.
empty?
a
end
##
# Return the DN as an escaped string.
def
to_s
@dn
end
##
# Return the DN as an escaped and normalized string.
def
to_s_normalized
self
.
class
.
new
(
*
to_a
).
to_s
end
# https://tools.ietf.org/html/rfc4514 section 2.4 lists these exceptions
# for DN values. All of the following must be escaped in any normal string
# using a single backslash ('\') as escape. The space character is left
# out here because in a "normalized" string, spaces should only be escaped
# if necessary (i.e. leading or trailing space).
NORMAL_ESCAPES
=
[
','
,
'+'
,
'"'
,
'\\'
,
'<'
,
'>'
,
';'
,
'='
].
freeze
# The following must be represented as escaped hex
HEX_ESCAPES
=
{
"
\n
"
=>
'\0a'
,
"
\r
"
=>
'\0d'
}.
freeze
# 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
# string.
ESCAPE_RE
=
Regexp
.
new
(
"(^ |^#| $|["
+
NORMAL_ESCAPES
.
map
{
|
e
|
Regexp
.
escape
(
e
)
}.
join
+
"])"
)
HEX_ESCAPE_RE
=
Regexp
.
new
(
"(["
+
HEX_ESCAPES
.
keys
.
map
{
|
e
|
Regexp
.
escape
(
e
)
}.
join
+
"])"
)
##
# Escape a string for use in a DN value
def
self
.
escape
(
string
)
escaped
=
string
.
gsub
(
ESCAPE_RE
)
{
|
char
|
"
\\
"
+
char
}
escaped
.
gsub
(
HEX_ESCAPE_RE
)
{
|
char
|
HEX_ESCAPES
[
char
]
}
end
##
# Proxy all other requests to the string object, because a DN is mainly
# used within the library as a string
# rubocop:disable GitlabSecurity/PublicSend
def
method_missing
(
method
,
*
args
,
&
block
)
@dn
.
send
(
method
,
*
args
,
&
block
)
end
end
end
end
def
up
ldap_identities
=
Identity
.
where
(
"provider like 'ldap%'"
)
ldap_identities
.
find_each
do
|
identity
|
begin
identity
.
extern_uid
=
Gitlab
::
LDAP
::
DN
.
new
(
identity
.
extern_uid
).
to_s_normalized
unless
identity
.
save
say
"Unable to normalize
\"
#{
identity
.
extern_uid
}
\"
. Skipping."
end
rescue
Gitlab
::
LDAP
::
MalformedDnError
,
Gitlab
::
LDAP
::
UnsupportedDnFormatError
=>
e
say
"Unable to normalize
\"
#{
identity
.
extern_uid
}
\"
due to
\"
#{
e
.
message
}
\"
. Skipping."
end
end
end
def
down
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment