PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/mod_perl/GodAuth.pm

http://github.com/exflickr/GodAuth
Perl | 326 lines | 197 code | 82 blank | 47 comment | 23 complexity | 73797cbd78c110a26b6a6b77eaaf40f9 MD5 | raw file
  1. package GodAuth;
  2. use warnings;
  3. use strict;
  4. use GodAuthConfig;
  5. use Apache2::RequestRec ();
  6. use Apache2::Connection;
  7. use Apache2::Const -compile => qw(OK REDIRECT REMOTE_NOLOOKUP FORBIDDEN);
  8. use APR::Table;
  9. use Digest::SHA1 qw(sha1_hex);
  10. use MIME::Base64;
  11. use Data::Dumper;
  12. use Sys::Hostname;
  13. our $last_reload_time = time();
  14. our $reload_timeout = 60; # per apache process!
  15. $| = 1;
  16. ##############################################################################################################
  17. sub handler {
  18. #
  19. # get URL
  20. #
  21. my $r = shift;
  22. my $domain = $r->headers_in->{'Host'} || 'UNKNOWN-HOST';
  23. my $path = $r->unparsed_uri;
  24. my $host = hostname;
  25. $ENV{GodAuth_User} = '';
  26. my $url = $domain . $path;
  27. my $log = "$$ URL : $url";
  28. #########################################################
  29. #
  30. # reload the config?
  31. #
  32. if (time() - $GodAuth::last_reload_time > $GodAuth::reload_timeout){
  33. &GodAuth::reload_config();
  34. $GodAuth::last_reload_time = time();
  35. }
  36. #########################################################
  37. #
  38. # 1) check we have a cookie secret
  39. #
  40. if (!$GodAuthConfig::CookieSecret){
  41. $GodAuthConfig::CookieSecret = 'nottherightsecret';
  42. }
  43. #########################################################
  44. #
  45. # 1) determine if we need to perform access control for this url
  46. #
  47. my $allow = 'none';
  48. for my $obj (@{$GodAuthConfig::PermMap}){
  49. if ($url =~ $obj->{url}){
  50. $allow = $obj->{who};
  51. last;
  52. }
  53. }
  54. $log .= " $allow";
  55. #########################################################
  56. #
  57. # 2) we might need auth - see if we have a valid cookie
  58. #
  59. my $cookie_is_valid = 0;
  60. my $cookie_user = '?';
  61. my $cookie_roles = '_';
  62. my $cookie_is_old = 0;
  63. my $cookie_age = 0;
  64. my $cookie_is_future = 0;
  65. my $cookies = &parse_cookie_jar($r->headers_in->{'Cookie'});
  66. my $cookie = $cookies->{$GodAuthConfig::CookieName};
  67. if ($cookie){
  68. my ($user, $roles, $ts, $hmac) = split '-', $cookie, 4;
  69. my $ua = $r->headers_in->{'User-Agent'};
  70. if ($ua =~ /AppleWebKit/) {
  71. $ua = "StupidAppleWebkitHacksGRRR";
  72. }
  73. $ua =~ s/ FirePHP\/\d+\.\d+//;
  74. my $raw = "$user-$roles-$ts-$ua";
  75. #&xlog("COOKIE: $cookie $raw\n");
  76. my $hmac2 = sha1_hex( $GodAuthConfig::CookieSecret . $raw );
  77. if ($hmac eq $hmac2){
  78. #
  79. # check that our cookie isn't too old
  80. #
  81. $cookie_age = time() - $ts;
  82. $ENV{GodAuth_Cookie_Age} = $cookie_age;
  83. if ($ts < time() - 8 * 60 * 60 && $user !~ /\:/){
  84. #
  85. # cookie is old (only for non-alpha users
  86. #
  87. $cookie_is_old = 1;
  88. $cookie_age = time() - $ts;
  89. $log .= " (bad cookie ts $ts - it's too old - $cookie_age seconds)";
  90. }elsif ($ts > time() + 5 * 60){
  91. #
  92. # cookie starts in the future - wtf
  93. #
  94. $cookie_is_future = 1;
  95. $log .= " (bad cookie ts $ts - it starts in the future)";
  96. }else{
  97. $cookie_is_valid = 1;
  98. $cookie_user = $user;
  99. $cookie_roles = $roles;
  100. $r->headers_in->set('GodAuth-User', $cookie_user);
  101. $r->headers_in->set('GodAuth-Roles', $cookie_roles);
  102. $ENV{GodAuth_User} = $cookie_user;
  103. $ENV{GodAuth_Roles} = $cookie_roles;
  104. $r->notes->add("GodAuth_User" => $cookie_user);
  105. $r->notes->add("GodAuth_Roles" => $cookie_roles);
  106. $log .= " (cookie: $cookie_user $cookie_roles)";
  107. }
  108. }else{
  109. $log .= " (bad cookie hmac [$GodAuthConfig::CookieSecret$user-$ts-$ua] -> $hmac2 vs $hmac)";
  110. }
  111. }else{
  112. $log .= " (no cookie)";
  113. }
  114. &xlog($log."\n");
  115. #########################################################
  116. #
  117. # 3) exit now if we got an 'all'
  118. #
  119. if (ref $allow ne 'ARRAY'){
  120. if ($allow eq 'all'){
  121. return Apache2::Const::OK;
  122. }
  123. }
  124. #########################################################
  125. #
  126. # 4) if we don't have a valid cookie, redirect to the auther
  127. #
  128. if (!$cookie){
  129. return &redir($r, $url, $GodAuthConfig::FailNeedsAuth);
  130. }
  131. if ($cookie_is_old){
  132. return &redir($r, $url, $GodAuthConfig::FailCookieOld);
  133. }
  134. if ($cookie_is_future){
  135. return &redir($r, $url, $GodAuthConfig::FailCookieFuture);
  136. }
  137. if (!$cookie_is_valid){
  138. return &redir($r, $url, $GodAuthConfig::FailCookieInvalid);
  139. }
  140. #########################################################
  141. #
  142. # 5) exit now for authed
  143. #
  144. if (ref $allow ne 'ARRAY'){
  145. if ($allow eq 'authed'){
  146. return Apache2::Const::OK;
  147. }
  148. }
  149. #########################################################
  150. #
  151. # 5) now we need to match usernames and/or roles
  152. #
  153. # get arrayref of allowed roles
  154. unless (ref $allow eq 'ARRAY'){
  155. $allow = [$allow];
  156. }
  157. # get arrayref of our roles
  158. my $matches = [$cookie_user];
  159. for my $role(split /,/, $cookie_roles){
  160. if ($role ne '_'){
  161. push @{$matches}, 'role:'.$role;
  162. }
  163. }
  164. for my $a (@{$allow}){
  165. for my $b (@{$matches}){
  166. if ($a eq $b){
  167. return Apache2::Const::OK;
  168. }
  169. }
  170. }
  171. #
  172. # send the user to the not-on-list page
  173. #
  174. return &redir($r, $url, $GodAuthConfig::FailNotOnList);
  175. }
  176. ##############################################################################################################
  177. sub redir {
  178. my ($r, $ref, $url) = @_;
  179. $ref = &urlencode('http://'.$ref);
  180. $url .= ($url =~ /\?/) ? "&ref=$ref" : "?ref=$ref";
  181. $r->headers_out->set('Location', $url);
  182. return Apache2::Const::REDIRECT;
  183. }
  184. ##############################################################################################################
  185. sub xlog {
  186. return unless $GodAuthConfig::LogFile;
  187. open F, '>>'.$GodAuthConfig::LogFile;
  188. print F $_[0];
  189. close F;
  190. }
  191. ##############################################################################################################
  192. sub parse_cookie_jar {
  193. my ($jar) = @_;
  194. return {} unless defined $jar;
  195. my @bits = split /;\s*/, $jar;
  196. my $out = {};
  197. for my $bit (@bits){
  198. my ($k, $v) = split '=', $bit, 2;
  199. $k = &urldecode($k);
  200. $v = &urldecode($v);
  201. $out->{$k} = $v;
  202. }
  203. return $out;
  204. }
  205. ##############################################################################################################
  206. sub urldecode {
  207. $_[0] =~ s!\+! !g;
  208. $_[0] =~ s/%([a-fA-F0-9]{2,2})/chr(hex($1))/eg;
  209. return $_[0];
  210. }
  211. sub urlencode {
  212. $_[0] =~ s!([^a-zA-Z0-9-_ ])! sprintf('%%%02x', ord $1) !gex;
  213. $_[0] =~ s! !+!g;
  214. return $_[0];
  215. }
  216. ##############################################################################################################
  217. sub reload_config {
  218. open F, "/usr/local/wwwGodAuth/GodAuthConfig.pm";
  219. my $data = '';
  220. while (<F>){
  221. $data .= $_;
  222. }
  223. close F;
  224. eval $data;
  225. }
  226. ##############################################################################################################
  227. 1;