| 1 | # $Id$ |
|---|
| 2 | use strict; |
|---|
| 3 | my $number = 38; |
|---|
| 4 | |
|---|
| 5 | use Test::More; |
|---|
| 6 | |
|---|
| 7 | use lib 't/lib', 'extlib', 'lib', '../lib', '../extlib'; |
|---|
| 8 | use MT; |
|---|
| 9 | |
|---|
| 10 | use vars qw( $DB_DIR $T_CFG ); |
|---|
| 11 | use MT::Test; |
|---|
| 12 | my $mt = MT->new() or die MT->errstr; |
|---|
| 13 | |
|---|
| 14 | SKIP: { |
|---|
| 15 | if ( ! MT->component('enterprise') ) { |
|---|
| 16 | plan skip_all => "Enterprise pack is not installed."; |
|---|
| 17 | } else { |
|---|
| 18 | plan tests => $number; |
|---|
| 19 | } |
|---|
| 20 | |
|---|
| 21 | eval "require Net::LDAP;"; |
|---|
| 22 | if ($@) { |
|---|
| 23 | skip "Net::LDAP is not installed.", $number; |
|---|
| 24 | } |
|---|
| 25 | eval "require MT::LDAP;"; |
|---|
| 26 | if ($@) { |
|---|
| 27 | skip "MT::LDAP is not found. Did you enable Enterprise Pack?", $number; |
|---|
| 28 | } |
|---|
| 29 | |
|---|
| 30 | require MT::Author; |
|---|
| 31 | require MT::Auth; |
|---|
| 32 | require MT::LDAP; |
|---|
| 33 | MT::Test->import( qw( :db :test ) ); |
|---|
| 34 | |
|---|
| 35 | &ldapdelete( name => 'Bob D' ); |
|---|
| 36 | &ldapdelete( name => 'Axl Rose' ); |
|---|
| 37 | &ldapdelete( name => 'Chuck D' ); |
|---|
| 38 | &ldapdelete( name => 'Melody' ); |
|---|
| 39 | |
|---|
| 40 | if (!MT->config->LDAPUserIdAttribute) { |
|---|
| 41 | print "Set LDAPUserIdAttribute directive or this test will fail.\n"; |
|---|
| 42 | } |
|---|
| 43 | { |
|---|
| 44 | &ldapadd( |
|---|
| 45 | name => 'Bob D', |
|---|
| 46 | email => 'bobd@example.com', |
|---|
| 47 | displayName => 'Dylan', |
|---|
| 48 | ); |
|---|
| 49 | my ($entry) = &ldapsearch( |
|---|
| 50 | filter => '(cn=Bob D)', |
|---|
| 51 | attrs => [MT->config->LDAPUserIdAttribute] |
|---|
| 52 | ); |
|---|
| 53 | |
|---|
| 54 | my $author = MT::Author->load({ name => 'Bob D' }); |
|---|
| 55 | ok($author); |
|---|
| 56 | ok($author->is_active); |
|---|
| 57 | $author->external_id($entry->get_value(MT->config->LDAPUserIdAttribute)); |
|---|
| 58 | $author->save; |
|---|
| 59 | |
|---|
| 60 | &ldapadd( |
|---|
| 61 | name => 'Chuck D', |
|---|
| 62 | email => 'chuckd@example.com', |
|---|
| 63 | displayName => 'Chuck', |
|---|
| 64 | ); |
|---|
| 65 | my ($entry) = &ldapsearch( |
|---|
| 66 | filter => '(cn=Chuck D)', |
|---|
| 67 | attrs => [MT->config->LDAPUserIdAttribute] |
|---|
| 68 | ); |
|---|
| 69 | |
|---|
| 70 | my $author0 = MT::Author->load({ name => 'Chuck D' }); |
|---|
| 71 | ok($author0); |
|---|
| 72 | ok($author0->is_active); |
|---|
| 73 | $author0->external_id($entry->get_value(MT->config->LDAPUserIdAttribute)); |
|---|
| 74 | $author0->save; |
|---|
| 75 | |
|---|
| 76 | &ldapadd( |
|---|
| 77 | name => 'Melody', |
|---|
| 78 | email => 'm@example.com', |
|---|
| 79 | displayName => 'Melody Nelson', |
|---|
| 80 | ); |
|---|
| 81 | my ($entry) = &ldapsearch( |
|---|
| 82 | filter => '(cn=Melody)', |
|---|
| 83 | attrs => [MT->config->LDAPUserIdAttribute] |
|---|
| 84 | ); |
|---|
| 85 | |
|---|
| 86 | my $author00 = MT::Author->load({ name => 'Melody' }); |
|---|
| 87 | ok($author00); |
|---|
| 88 | ok($author00->is_active); |
|---|
| 89 | $author00->external_id($entry->get_value(MT->config->LDAPUserIdAttribute)); |
|---|
| 90 | $author00->save; |
|---|
| 91 | |
|---|
| 92 | ok(0 == MT::Auth->synchronize); # nobody will be synched-updated at this point |
|---|
| 93 | |
|---|
| 94 | my $author2 = MT::Author->load({ name => 'Bob D' }, {cached_ok=>0}); |
|---|
| 95 | is($author->id, $author2->id); |
|---|
| 96 | is($author->name, $author2->name); |
|---|
| 97 | is($author->email, $author2->email); |
|---|
| 98 | is($author->nickname, $author2->nickname); |
|---|
| 99 | is($author->type, $author2->type); |
|---|
| 100 | ok($author2->is_active); |
|---|
| 101 | |
|---|
| 102 | &ldapmodify( |
|---|
| 103 | name => 'Bob D', |
|---|
| 104 | newname => 'Axl Rose', |
|---|
| 105 | newemail => 'axl@example.com', |
|---|
| 106 | newnick => 'William Axl Rose' |
|---|
| 107 | ); |
|---|
| 108 | |
|---|
| 109 | is(MT::Auth->synchronize, 1); |
|---|
| 110 | |
|---|
| 111 | my $authorX = MT::Author->load({ name => 'Bob D' }, {cached_ok=>0}); |
|---|
| 112 | ok(!$authorX); |
|---|
| 113 | my $author3 = MT::Author->load({ name => 'Axl Rose' }, {cached_ok=>0}); |
|---|
| 114 | ok($author3); |
|---|
| 115 | is($author2->id, $author3->id); |
|---|
| 116 | is('Axl Rose', $author3->name); |
|---|
| 117 | is($author2->email, $author3->email); |
|---|
| 118 | is($author2->nickname, $author3->nickname); |
|---|
| 119 | is($author3->type, $author2->type); |
|---|
| 120 | ok($author3->is_active); |
|---|
| 121 | |
|---|
| 122 | &ldapmodrdn( |
|---|
| 123 | name => 'Axl Rose', |
|---|
| 124 | newsuperior => 'ou=MT', |
|---|
| 125 | ); |
|---|
| 126 | |
|---|
| 127 | my $cfg = MT->config; |
|---|
| 128 | my $ldapurl = $cfg->LDAPAuthURL; |
|---|
| 129 | |
|---|
| 130 | ## typo will not make users inactive |
|---|
| 131 | $cfg->LDAPAuthURL('ldap://trac.sixapart.jp/dc=typo,dc=sixapart,dc=jp'); |
|---|
| 132 | |
|---|
| 133 | ok(!MT::Auth->synchronize); |
|---|
| 134 | |
|---|
| 135 | my $author4 = MT::Author->load({ name => 'Axl Rose' }, {cached_ok=>0}); |
|---|
| 136 | ok($author4->is_active); |
|---|
| 137 | is($author4->external_id, $author3->external_id); |
|---|
| 138 | |
|---|
| 139 | my $author6 = MT::Author->load({ name => 'Chuck D' }, {cached_ok=>0}); |
|---|
| 140 | ok($author6->is_active); |
|---|
| 141 | my $author7 = MT::Author->load({ name => 'Melody' }, {cached_ok=>0}); |
|---|
| 142 | ok($author7->is_active); |
|---|
| 143 | |
|---|
| 144 | ## typo(but some users do exist in the directory) will not make users inactive |
|---|
| 145 | ## unless there is at lease one sysadmin in the (wrong) hierarchy |
|---|
| 146 | my $wrong_url = $ldapurl; |
|---|
| 147 | $wrong_url =~ s!(ldap://.+)/(.+)!$1/ou=MT,$2!; |
|---|
| 148 | $cfg->LDAPAuthURL($wrong_url); |
|---|
| 149 | |
|---|
| 150 | ok(!MT::Auth->synchronize); |
|---|
| 151 | |
|---|
| 152 | my $author8 = MT::Author->load({ name => 'Axl Rose' }, {cached_ok=>0}); |
|---|
| 153 | ok($author8->is_active); |
|---|
| 154 | is($author8->external_id, $author3->external_id); |
|---|
| 155 | |
|---|
| 156 | my $author9 = MT::Author->load({ name => 'Chuck D' }, {cached_ok=>0}); |
|---|
| 157 | ok($author9->is_active); |
|---|
| 158 | my $author10 = MT::Author->load({ name => 'Melody' }, {cached_ok=>0}); |
|---|
| 159 | ok($author10->is_active); |
|---|
| 160 | |
|---|
| 161 | &ldapmodrdn( name => 'Axl Rose' ); |
|---|
| 162 | |
|---|
| 163 | $cfg->LDAPAuthURL($ldapurl); |
|---|
| 164 | |
|---|
| 165 | &ldapdelete( name => 'Axl Rose' ); |
|---|
| 166 | |
|---|
| 167 | ok(MT::Auth->synchronize); |
|---|
| 168 | |
|---|
| 169 | my $author11 = MT::Author->load({ name => 'Axl Rose' }, {cached_ok=>0}); |
|---|
| 170 | ok(!$author11->is_active); |
|---|
| 171 | is($author11->external_id, $author3->external_id); |
|---|
| 172 | |
|---|
| 173 | &ldapadd( |
|---|
| 174 | name => 'Axl Rose', |
|---|
| 175 | email => 'axl@example.com', |
|---|
| 176 | displayName => 'William Axl Rose', |
|---|
| 177 | ); |
|---|
| 178 | |
|---|
| 179 | ok(0 == MT::Auth->synchronize); ## this time it does not sync anyone -- synchronize returns 0 |
|---|
| 180 | |
|---|
| 181 | my $author5 = MT::Author->load({ name => 'Axl Rose' }, {cached_ok=>0}); |
|---|
| 182 | ok($author5); |
|---|
| 183 | ok(!$author5->is_active); ## make sure newly created user with the same name does not re-activate an author. |
|---|
| 184 | |
|---|
| 185 | &ldapdelete( name => 'Axl Rose' ); |
|---|
| 186 | &ldapdelete( name => 'Chuck D' ); |
|---|
| 187 | &ldapdelete( name => 'Melody' ); |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | } # end of SKIP block |
|---|
| 191 | |
|---|
| 192 | sub _ldapbind { |
|---|
| 193 | my ($auth, $ldap) = @_; |
|---|
| 194 | my $res; |
|---|
| 195 | my $base = $auth->{base}; |
|---|
| 196 | my $bind_dn = $auth->{bind_dn}; |
|---|
| 197 | my $bind_password = $auth->{bind_password}; |
|---|
| 198 | my $sasl_mechanism = $auth->{sasl_mechanism}; |
|---|
| 199 | my $uid_attr_name = $auth->{uid_attr_name}; |
|---|
| 200 | my $filter = $auth->{filter}; |
|---|
| 201 | my $scope = $auth->{scope}; |
|---|
| 202 | if (!$bind_dn) { |
|---|
| 203 | $res = $ldap->bind; |
|---|
| 204 | } else { |
|---|
| 205 | if ($sasl_mechanism eq 'PLAIN') { |
|---|
| 206 | $res = $ldap->bind($bind_dn, password => $bind_password); |
|---|
| 207 | } else { |
|---|
| 208 | require Authen::SASL; |
|---|
| 209 | my $sasl = Authen::SASL->new( |
|---|
| 210 | mechanism => $sasl_mechanism, |
|---|
| 211 | callback => { |
|---|
| 212 | pass => $bind_password, |
|---|
| 213 | user => $bind_dn, |
|---|
| 214 | }, |
|---|
| 215 | ); |
|---|
| 216 | $res = $ldap->bind($bind_dn, sasl => $sasl); |
|---|
| 217 | } |
|---|
| 218 | } |
|---|
| 219 | 1; |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | sub ldapadd { |
|---|
| 223 | my (%opt) = @_; |
|---|
| 224 | my $auth = MT::LDAP->new; |
|---|
| 225 | my $ldap = $auth->ldap; |
|---|
| 226 | _ldapbind($auth, $ldap); |
|---|
| 227 | my $base = $auth->{base}; |
|---|
| 228 | my $dn = "cn=$opt{name},$base"; |
|---|
| 229 | my $result = $ldap->add( $dn, |
|---|
| 230 | attr => [ |
|---|
| 231 | $auth->{uid_attr_name} => [$opt{name}], |
|---|
| 232 | 'cn' => [$opt{name}], |
|---|
| 233 | 'sn' => $opt{displayName}, |
|---|
| 234 | MT->config->LDAPUserEmailAttribute => $opt{email}, |
|---|
| 235 | 'objectclass' => ['top', 'person', |
|---|
| 236 | 'organizationalPerson', |
|---|
| 237 | 'inetOrgPerson' ], |
|---|
| 238 | ] |
|---|
| 239 | ); |
|---|
| 240 | $result->code && warn "failed to add entry: ", $result->error ; |
|---|
| 241 | my $mesg = $ldap->unbind; # take down session |
|---|
| 242 | 1; |
|---|
| 243 | } |
|---|
| 244 | |
|---|
| 245 | sub ldapmodrdn { |
|---|
| 246 | my (%opt) = @_; |
|---|
| 247 | my $auth = MT::LDAP->new; |
|---|
| 248 | my $ldap = $auth->{ldap}; |
|---|
| 249 | _ldapbind($auth, $ldap); |
|---|
| 250 | my $base = $auth->{base}; |
|---|
| 251 | my $dn = "cn=$opt{name},$base"; |
|---|
| 252 | my %param; |
|---|
| 253 | $param{newrdn} = "cn=$opt{name}"; |
|---|
| 254 | if (exists $opt{newsuperior}) { |
|---|
| 255 | $param{newsuperior} = "$opt{newsuperior},$base"; |
|---|
| 256 | } else { |
|---|
| 257 | my $new = $base; |
|---|
| 258 | $new =~ s!([^,]+),(.+)!$2!; |
|---|
| 259 | $param{newsuperior} = $new; |
|---|
| 260 | } |
|---|
| 261 | my $result = $ldap->moddn( $dn, %param ); |
|---|
| 262 | $result->code && warn "failed to change rdn of the entry: ", $result->error ; |
|---|
| 263 | $result = $ldap->unbind; # take down session |
|---|
| 264 | 1; |
|---|
| 265 | } |
|---|
| 266 | |
|---|
| 267 | sub ldapmodify { |
|---|
| 268 | my (%opt) = @_; |
|---|
| 269 | my $auth = MT::LDAP->new; |
|---|
| 270 | my $ldap = $auth->{ldap}; |
|---|
| 271 | _ldapbind($auth, $ldap); |
|---|
| 272 | my $base = $auth->{base}; |
|---|
| 273 | my $dn = "cn=$opt{name},$base"; |
|---|
| 274 | my $result = $ldap->moddn( $dn, newrdn => "cn=$opt{newname}", deleteoldrdn => 1 ); |
|---|
| 275 | warn $result->error if $result->code; |
|---|
| 276 | $dn = "cn=$opt{newname},$base"; |
|---|
| 277 | $result = $ldap->modify( $dn, |
|---|
| 278 | changes => [replace => [ |
|---|
| 279 | $auth->{uid_attr_name} => [$opt{newname}], |
|---|
| 280 | 'cn' => $opt{newname}, |
|---|
| 281 | 'sn' => $opt{newname}, |
|---|
| 282 | MT->config->LDAPUserEmailAttribute => $opt{newemail}, |
|---|
| 283 | ] ] |
|---|
| 284 | ); |
|---|
| 285 | $result->code && warn "failed to modify entry $opt{name} -> $opt{newname}: ", $result->error ; |
|---|
| 286 | $result = $ldap->unbind; # take down session |
|---|
| 287 | 1; |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | sub ldapdelete { |
|---|
| 291 | my (%opt) = @_; |
|---|
| 292 | my $auth = MT::LDAP->new; |
|---|
| 293 | my $ldap = $auth->{ldap}; |
|---|
| 294 | _ldapbind($auth, $ldap); |
|---|
| 295 | my $base = $auth->{base}; |
|---|
| 296 | my $dn = "cn=$opt{name},$base"; |
|---|
| 297 | my $result = $ldap->delete($dn); |
|---|
| 298 | $result->code && warn "failed to delete entry: ", $result->error ; |
|---|
| 299 | my $mesg = $ldap->unbind; # take down session |
|---|
| 300 | 1; |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | sub ldapsearch { |
|---|
| 304 | my (%opt) = @_; |
|---|
| 305 | my $auth = MT::LDAP->new; |
|---|
| 306 | my $ldap = $auth->{ldap}; |
|---|
| 307 | _ldapbind($auth, $ldap); |
|---|
| 308 | my $base = $auth->{base}; |
|---|
| 309 | my $res = $ldap->search( |
|---|
| 310 | base => $base, |
|---|
| 311 | filter => $opt{filter}, |
|---|
| 312 | attrs => $opt{attrs}, |
|---|
| 313 | ); |
|---|
| 314 | $res->entries; |
|---|
| 315 | } |
|---|
| 316 | |
|---|
| 317 | 1; |
|---|