| 1 | use strict; |
|---|
| 2 | use POSIX; |
|---|
| 3 | |
|---|
| 4 | use Test::More tests => 11; |
|---|
| 5 | |
|---|
| 6 | # To keep away from being under FastCGI |
|---|
| 7 | $ENV{HTTP_HOST} = 'localhost'; |
|---|
| 8 | |
|---|
| 9 | use MT::Atom; |
|---|
| 10 | |
|---|
| 11 | use MT; |
|---|
| 12 | |
|---|
| 13 | use vars qw( $DB_DIR $T_CFG ); |
|---|
| 14 | my $mt = MT->new( Config => $T_CFG ) or die MT->errstr; |
|---|
| 15 | isa_ok($mt, 'MT'); |
|---|
| 16 | |
|---|
| 17 | use lib 't/lib', 'extlib', 'lib', '../lib', '../extlib'; |
|---|
| 18 | use MT::Test qw(:db :data); |
|---|
| 19 | |
|---|
| 20 | my $username = 'Chuck D'; |
|---|
| 21 | my $chuck = MT::Author->load({name => $username}) |
|---|
| 22 | or die "Couldn't load $username"; |
|---|
| 23 | my $chuck_token = $chuck->api_password; |
|---|
| 24 | |
|---|
| 25 | # not a good nonce-maker |
|---|
| 26 | my @hexch = ('0' .. '9', 'a' .. 'f'); |
|---|
| 27 | sub make_nonce { |
|---|
| 28 | join '', map { $hexch[rand() * @hexch] } (0..7); |
|---|
| 29 | } |
|---|
| 30 | sub make_wsse { |
|---|
| 31 | my ($password) = @_; |
|---|
| 32 | my $timestamp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time)); |
|---|
| 33 | my $nonce = make_nonce(); |
|---|
| 34 | my $PasswordDigest = sha1_base64($nonce . $timestamp . $chuck_token); |
|---|
| 35 | # print "# PasswordDigest is sha1('$nonce$timestamp$chuck_token')\n"; |
|---|
| 36 | $nonce = MIME::Base64::encode_base64($nonce, ''); |
|---|
| 37 | return "UserNameToken Username=\"$username\", " |
|---|
| 38 | . "PasswordDigest=\"$PasswordDigest\", Nonce=\"$nonce\", " |
|---|
| 39 | . "Created=\"$timestamp\""; |
|---|
| 40 | } |
|---|
| 41 | |
|---|
| 42 | require LWP::UserAgent::Local; |
|---|
| 43 | my $ua = new LWP::UserAgent::Local({ ScriptAlias => '/' }); |
|---|
| 44 | |
|---|
| 45 | { |
|---|
| 46 | # # # # First try a req with baloney auth, make sure it fails |
|---|
| 47 | # TBD: Try more bogus auth varieties |
|---|
| 48 | my $nonce = make_nonce(); |
|---|
| 49 | $nonce = MIME::Base64::encode_base64($nonce, ''); |
|---|
| 50 | |
|---|
| 51 | my $uri = new URI(); |
|---|
| 52 | $uri->path('/mt-atom.cgi/weblog/blog_id=1'); |
|---|
| 53 | my $req = new HTTP::Request(GET => $uri); |
|---|
| 54 | $req->header(Authentication => 'Atom'); |
|---|
| 55 | $req->header('X-WSSE' => "UserNameToken Username=\"Melody\", PasswordDigest=\"Oj12fART+XZvBZBe39vVvkirg4w\", Nonce=\"" . $nonce . "\", Created=\"2004-05-19T20:08:57Z\""); |
|---|
| 56 | print "# requesting: " . $req->uri . "\n"; |
|---|
| 57 | my $resp = $ua->request($req); |
|---|
| 58 | |
|---|
| 59 | print "# response code was: " . $resp->code . "\n"; |
|---|
| 60 | print "# content was: " . $resp->content . "\n";# if !$resp->code(); |
|---|
| 61 | ok($resp->is_error()); |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | |
|---|
| 65 | my $entry_id; |
|---|
| 66 | |
|---|
| 67 | use constant USE_DIGEST => 0; |
|---|
| 68 | |
|---|
| 69 | if (USE_DIGEST) |
|---|
| 70 | { |
|---|
| 71 | # # # # # # # use this to do a Digest auth with MD5 as the algorithm. |
|---|
| 72 | my $realm = 'movabletype'; |
|---|
| 73 | # $username = shift; |
|---|
| 74 | # $hashed_pwd = shift; |
|---|
| 75 | require Digest::MD5; |
|---|
| 76 | my $hashed_pwd = Digest::MD5::md5(join ':', $username, $realm, $chuck_token); |
|---|
| 77 | my $method = 'POST'; |
|---|
| 78 | |
|---|
| 79 | my $uri = new URI; |
|---|
| 80 | $uri->path('/mt-atom.cgi/weblog/blog_id=1'); |
|---|
| 81 | my $req = new HTTP::Request(POST => $uri); |
|---|
| 82 | |
|---|
| 83 | my $resp = $ua->simple_request($req); |
|---|
| 84 | ok($resp->is_error()); |
|---|
| 85 | my $auth_header = $resp->header('WWW-Authenticate'); |
|---|
| 86 | my ($nonce) = $auth_header =~ /nonce=(\S*)/; |
|---|
| 87 | |
|---|
| 88 | my $A1 = pack('H*', $hashed_pwd); |
|---|
| 89 | my $A2 = $method . ':' . $uri; |
|---|
| 90 | |
|---|
| 91 | require Digest::MD5; |
|---|
| 92 | my $hash = \&Digest::MD5::md5; |
|---|
| 93 | my $kd = sub { $hash->($_[0].':'.$_[1]) }; |
|---|
| 94 | |
|---|
| 95 | print STDERR "Signing ", join(':', |
|---|
| 96 | $nonce, |
|---|
| 97 | '', |
|---|
| 98 | '', |
|---|
| 99 | 'auth', |
|---|
| 100 | $hash->($A2)), "\n"; |
|---|
| 101 | print STDERR "with key ", $hash->($A1), "\n"; |
|---|
| 102 | |
|---|
| 103 | my $response = $kd->($hash->($A1), join(':', |
|---|
| 104 | $nonce, |
|---|
| 105 | '', |
|---|
| 106 | '', |
|---|
| 107 | 'auth', |
|---|
| 108 | $hash->($A2))); |
|---|
| 109 | print "# ", ($response = unpack('H*', $response)), "\n"; |
|---|
| 110 | |
|---|
| 111 | $uri = new URI; |
|---|
| 112 | $uri->path('/mt-atom.cgi/weblog/blog_id=1'); |
|---|
| 113 | $req = new HTTP::Request(POST => $uri); |
|---|
| 114 | |
|---|
| 115 | $req->header('X-Atom-Authorization', "Digest nonce=$nonce, username=$username, realm=movabletype, uri=$uri, qop=auth, response=$response"); |
|---|
| 116 | print "# sending: ", $req->header('X-Atom-Authorization'); |
|---|
| 117 | |
|---|
| 118 | $req->content(<<XML); |
|---|
| 119 | <?xml version="1.0" encoding="utf-8"?> |
|---|
| 120 | <entry xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> |
|---|
| 121 | <title>Fight the Power</title> |
|---|
| 122 | <content>Elvis was a hero to most but he never meant shit to me</content> |
|---|
| 123 | </entry> |
|---|
| 124 | XML |
|---|
| 125 | |
|---|
| 126 | $resp = $ua->simple_request($req); |
|---|
| 127 | |
|---|
| 128 | print "# " . $resp->code . " " . $resp->message . "\n"; |
|---|
| 129 | print "# " . $resp->content . "\n"; |
|---|
| 130 | ok($resp->is_success()); |
|---|
| 131 | |
|---|
| 132 | print "##\n". $resp->header('Location') . "\n##\n"; |
|---|
| 133 | my $location = $resp->header('Location') |
|---|
| 134 | || die "No Location: header. Did get\n" |
|---|
| 135 | . $resp->code() . $resp->message() . "\n" |
|---|
| 136 | . $resp->headers_as_string() . "\n" |
|---|
| 137 | . $resp->content(); |
|---|
| 138 | ($entry_id) = ($location =~ /entry_id=(\d+)/); |
|---|
| 139 | |
|---|
| 140 | $entry_id || die "Couldn't get entry ID from header Location: " |
|---|
| 141 | . $location; |
|---|
| 142 | |
|---|
| 143 | print "# entry ID is $entry_id\n"; |
|---|
| 144 | |
|---|
| 145 | ok($entry_id); |
|---|
| 146 | } |
|---|
| 147 | |
|---|
| 148 | unless (USE_DIGEST) |
|---|
| 149 | { |
|---|
| 150 | print "# Doing Digest auth\n"; |
|---|
| 151 | # # # # Now try posting an entry with authentication |
|---|
| 152 | my $nonce = make_nonce(); |
|---|
| 153 | my $timestamp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time)); |
|---|
| 154 | use Digest::SHA1 qw(sha1_base64); |
|---|
| 155 | my $PasswordDigest = sha1_base64($nonce . $timestamp . $chuck_token); |
|---|
| 156 | |
|---|
| 157 | # print STDERR ("Client hashing (", |
|---|
| 158 | # unpack('H*', $nonce . $timestamp . $chuck_token), ")\n"); |
|---|
| 159 | # print STDERR " produces: $PasswordDigest\n"; |
|---|
| 160 | |
|---|
| 161 | $nonce = MIME::Base64::encode_base64($nonce, ''); |
|---|
| 162 | |
|---|
| 163 | print "# nonce: $nonce\n"; |
|---|
| 164 | |
|---|
| 165 | my $dir = `pwd`; chomp $dir; |
|---|
| 166 | while (! -x ($dir . "/mt-atom.cgi")) { |
|---|
| 167 | $dir =~ s!(/[^/]*)$!!; |
|---|
| 168 | last if $dir =~ m!^/?$!; |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | my $uri = new URI; |
|---|
| 172 | $uri->path('/mt-atom.cgi/weblog/blog_id=1'); |
|---|
| 173 | my $req = new HTTP::Request(POST => $uri); |
|---|
| 174 | $req->header('Authorization' => 'Atom'); |
|---|
| 175 | $req->header('X-WSSE' => "UserNameToken Username=\"$username\", " |
|---|
| 176 | . "PasswordDigest=\"$PasswordDigest\", Nonce=\"$nonce\", " |
|---|
| 177 | . "Created=\"$timestamp\""); |
|---|
| 178 | |
|---|
| 179 | $req->content(<<XML); |
|---|
| 180 | <?xml version="1.0" encoding="utf-8"?> |
|---|
| 181 | <entry xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"> |
|---|
| 182 | <title>Fight the Power</title> |
|---|
| 183 | <content>Elvis was a hero to most but he never meant shit to me</content> |
|---|
| 184 | <issued>2004-08-06T00:43:34+01:00</issued> |
|---|
| 185 | </entry> |
|---|
| 186 | XML |
|---|
| 187 | |
|---|
| 188 | my $resp = $ua->simple_request($req); |
|---|
| 189 | |
|---|
| 190 | print "######### RESPONSE #########\n"; |
|---|
| 191 | print "# " . $resp->code . " " . $resp->message . "\n"; |
|---|
| 192 | my $content = $resp->content; |
|---|
| 193 | $content =~ s/^/# /gm; |
|---|
| 194 | print $content; |
|---|
| 195 | print "#########~RESPONSE #########\n"; |
|---|
| 196 | ok($resp->is_success()); |
|---|
| 197 | |
|---|
| 198 | #print "##\n". $resp->header('Location') . "\n##\n"; |
|---|
| 199 | my $location = $resp->header('Location') |
|---|
| 200 | || die "No Location: header. Did get\n" |
|---|
| 201 | . $resp->headers_as_string() . "\n" |
|---|
| 202 | . $resp->content(); |
|---|
| 203 | ($entry_id) = ($location =~ /entry_id=(\d+)/); |
|---|
| 204 | |
|---|
| 205 | $entry_id || die "Couldn't get entry ID from header Location: " |
|---|
| 206 | . $location; |
|---|
| 207 | |
|---|
| 208 | print "# entry ID is $entry_id\n"; |
|---|
| 209 | |
|---|
| 210 | ok($entry_id); |
|---|
| 211 | require XML::Atom; |
|---|
| 212 | my $atom_obj = XML::Atom::Entry->new(\$resp->content()); |
|---|
| 213 | require Date::Parse; |
|---|
| 214 | print "# ", $atom_obj->issued(), "\n"; |
|---|
| 215 | is(Date::Parse::str2time($atom_obj->issued()), |
|---|
| 216 | Date::Parse::str2time('2004-08-06T00:43:34+01:00'), $atom_obj->issued()); |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | { |
|---|
| 220 | my $wsse_header = make_wsse($chuck_token); |
|---|
| 221 | my $uri = new URI; |
|---|
| 222 | $uri->path("/mt-atom.cgi/weblog/blog_id=1/entry_id=$entry_id"); |
|---|
| 223 | my $req = new HTTP::Request(GET => $uri); |
|---|
| 224 | $req->header('Authorization' => 'Atom'); |
|---|
| 225 | $req->header('X-WSSE' => $wsse_header); |
|---|
| 226 | |
|---|
| 227 | print "# X-WSSSE: $wsse_header\n"; |
|---|
| 228 | |
|---|
| 229 | my $resp = $ua->request($req); |
|---|
| 230 | |
|---|
| 231 | require XML::LibXML; |
|---|
| 232 | my $atom_entry = XML::Atom::Entry->new(Stream => \$resp->content()); |
|---|
| 233 | |
|---|
| 234 | is($atom_entry->title(), "Fight the Power"); |
|---|
| 235 | is($atom_entry->author()->name(), $chuck->nickname); |
|---|
| 236 | is($atom_entry->content()->body(), |
|---|
| 237 | "Elvis was a hero to most but he never meant shit to me"); |
|---|
| 238 | |
|---|
| 239 | $wsse_header = make_wsse($chuck_token); |
|---|
| 240 | $uri = new URI; |
|---|
| 241 | $uri->path("/mt-atom.cgi/weblog/blog_id=1/entry_id=$entry_id"); |
|---|
| 242 | $req = new HTTP::Request(PUT => $uri); |
|---|
| 243 | $req->header('Authorization' => 'Atom'); |
|---|
| 244 | $req->header('X-WSSE' => $wsse_header); |
|---|
| 245 | my $body = $atom_entry->as_xml(); |
|---|
| 246 | $req->content($body); |
|---|
| 247 | |
|---|
| 248 | $resp = $ua->request($req); |
|---|
| 249 | |
|---|
| 250 | if (ok($resp->is_success)) { |
|---|
| 251 | $atom_entry = XML::Atom::Entry->new(Stream => \$resp->content()); |
|---|
| 252 | is($atom_entry->title, "Fight the Power"); |
|---|
| 253 | is(Date::Parse::str2time($atom_entry->issued), |
|---|
| 254 | Date::Parse::str2time("2004-08-05T21:13:34-0230")); |
|---|
| 255 | } else { |
|---|
| 256 | print STDERR "# PUT request returned ", $resp->status_line(), "\n"; |
|---|
| 257 | skip(1); |
|---|
| 258 | skip(1); |
|---|
| 259 | } |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | END { |
|---|
| 263 | #my $melody = MT::Author->load({ name => $username }); |
|---|
| 264 | #$melody->delete() if $melody; |
|---|
| 265 | } |
|---|