Browse Source

Initial import

tags/0.5
Chris Smith 15 years ago
commit
95f1b0b960
13 changed files with 1842 additions and 0 deletions
  1. 275
    0
      bigmath.inc.php
  2. 190
    0
      discoverer.inc.php
  3. 128
    0
      examples/basic/index.php
  4. 48
    0
      examples/javascript/iframe.php
  5. 132
    0
      examples/javascript/index.php
  6. 1
    0
      keycache.php
  7. 362
    0
      keymanager.inc.php
  8. BIN
      openid.gif
  9. 53
    0
      poster.inc.php
  10. 310
    0
      processor.php
  11. 48
    0
      sreg.inc.php
  12. 106
    0
      test.php
  13. 189
    0
      urlbuilder.inc.php

+ 275
- 0
bigmath.inc.php View File

@@ -0,0 +1,275 @@
1
+<?php
2
+
3
+/**
4
+ * BigMath: A math library wrapper that abstracts out the underlying
5
+ * long integer library.
6
+ *
7
+ * Original code (C) 2005 JanRain <openid@janrain.com>
8
+ * Modifications (C) 2007 Stephen Bounds.
9
+ * Further modifications (C) 2008 Chris Smith.
10
+ *
11
+ * Licensed under the LGPL.
12
+ */
13
+
14
+/**
15
+ * Base BigMath class which will be extended by a big-integer math library
16
+ * such as bcmath or gmp. 
17
+ */
18
+abstract class BigMath {
19
+
20
+  /** File handle for our random data source. */
21
+  protected $randsource = false;
22
+
23
+  /** Duplicate cache for rand(). */
24
+  protected $duplicate_cache = array();
25
+
26
+  /** Singleton reference to our bigmath class. */
27
+  private static $me = null;
28
+
29
+  /**
30
+   * Converts the specified positive integer to the shortest possible
31
+   * big-endian, two's complement representation.
32
+   * 
33
+   * @param long The integer to be converted
34
+   * @return the btwoc representation of the integer
35
+   */    
36
+  public function btwoc($long) {
37
+    $cmp = $this->cmp($long, 0);
38
+
39
+    if ($cmp < 0) {
40
+      throw new Exception('$long must be a positive integer.');
41
+    } else if ($cmp == 0) {
42
+      return "\x00";
43
+    }
44
+
45
+    $bytes = array();
46
+
47
+    while ($this->cmp($long, 0) > 0) {
48
+      array_unshift($bytes, $this->mod($long, 256));
49
+      $long = $this->div($long, 256);
50
+    }
51
+
52
+    if ($bytes && ($bytes[0] > 127)) {
53
+      array_unshift($bytes, 0);
54
+    }
55
+
56
+    // Convert to \xHH\xHH... format and return
57
+    $string = '';
58
+
59
+    foreach ($bytes as $byte) {
60
+      $string .= pack('C', $byte);
61
+    }
62
+
63
+    return $string;
64
+  }
65
+
66
+  /**
67
+   * Converts the specified btwoc representation of an integer back to the
68
+   * original integer.
69
+   *
70
+   * @param str The btwoc representation to be "undone"
71
+   * @return The corresponding integer 
72
+   */
73
+  public function btwoc_undo($str) {
74
+    if ($str == null) {
75
+      return null;
76
+    }
77
+
78
+    $bytes = array_values(unpack('C*', $str));
79
+
80
+    $n = $this->init(0);
81
+
82
+    if ($bytes && ($bytes[0] > 127)) {
83
+      throw new Exception('$str must represent a positive integer');
84
+    }
85
+
86
+    foreach ($bytes as $byte) {
87
+      $n = $this->mul($n, 256);
88
+      $n = $this->add($n, $byte);
89
+    }
90
+
91
+    return $n;
92
+  }
93
+
94
+  /**
95
+   * Returns a random number up to the specified maximum.
96
+   *
97
+   * @param max The maximum value to return
98
+   * @return A random number between 0 and the specified max 
99
+   */
100
+  public function rand($max) {
101
+    // Used as the key for the duplicate cache
102
+    $rbytes = $this->btwoc($max);
103
+
104
+    if (array_key_exists($rbytes, $this->duplicate_cache)) {
105
+      list($duplicate, $nbytes) = $this->duplicate_cache[$rbytes];
106
+    } else {
107
+      if ($rbytes[0] == "\x00") {
108
+        $nbytes = strlen($rbytes) - 1;
109
+      } else {
110
+        $nbytes = strlen($rbytes);
111
+      }
112
+
113
+      $mxrand = $this->pow(256, $nbytes);
114
+
115
+      // If we get a number less than this, then it is in the
116
+      // duplicated range.
117
+      $duplicate = $this->mod($mxrand, $max);
118
+
119
+      if (count($this->duplicate_cache) > 10) {
120
+        $this->duplicate_cache = array();
121
+      }
122
+
123
+      $this->duplicate_cache[$rbytes] = array($duplicate, $nbytes);
124
+    }
125
+
126
+    do {
127
+      $bytes = "\x00" . $this->getRandomBytes($nbytes);
128
+      $n = $this->btwoc_undo($bytes);
129
+      // Keep looping if this value is in the low duplicated range
130
+    } while ($this->cmp($n, $duplicate) < 0);
131
+
132
+    return $this->mod($n, $max);
133
+  }
134
+
135
+  /**
136
+   * Get the specified number of random bytes.
137
+   *
138
+   * Attempts to use a cryptographically secure (not predictable)
139
+   * source of randomness. If there is no high-entropy
140
+   * randomness source available, it will fail.
141
+
142
+   * @param num_bytes The number of bytes to retrieve
143
+   * @return The specified number of random bytes
144
+   */
145
+  public function getRandomBytes($num_bytes) {
146
+    if (!$this->randsource) {
147
+     $this->randsource = @fopen('/dev/urandom', 'r');
148
+    }
149
+
150
+    if ($this->randsource) {
151
+      return fread($this->randsource, $num_bytes);
152
+    } else {
153
+      // pseudorandom used
154
+      $bytes = '';
155
+      for ($i = 0; $i < $num_bytes; $i += 4) {
156
+        $bytes .= pack('L', mt_rand());
157
+      }
158
+      return substr($bytes, 0, $num_bytes);
159
+    }      
160
+  }
161
+
162
+  public abstract function init($number, $base = 10);
163
+
164
+  public abstract function add($x, $y);
165
+  public abstract function sub($x, $y);
166
+  public abstract function mul($x, $y);
167
+  public abstract function div($x, $y);
168
+  public abstract function cmp($x, $y);
169
+
170
+  public abstract function mod($base, $modulus);
171
+  public abstract function pow($base, $exponent);
172
+
173
+  public abstract function powmod($base, $exponent, $modulus);
174
+
175
+  public abstract function toString($num);
176
+
177
+  /**
178
+   * Detect which math library is available
179
+   *
180
+   * @return The extension details of the first available extension,
181
+   * or false if no extensions are available.
182
+   */
183
+  private static function BigMath_Detect() {
184
+    $extensions = array(
185
+  	array('modules' => array('gmp', 'php_gmp'),
186
+              'extension' => 'gmp',
187
+              'class' => 'BigMath_GmpMathWrapper'),
188
+  	array('modules' => array('bcmath', 'php_bcmath'),
189
+              'extension' => 'bcmath',
190
+              'class' => 'BigMath_BcMathWrapper')
191
+    );
192
+
193
+    $loaded = false;
194
+    foreach ($extensions as $ext) {
195
+      // See if the extension specified is already loaded.
196
+      if ($ext['extension'] && extension_loaded($ext['extension'])) {
197
+        $loaded = true;
198
+      }
199
+
200
+      // Try to load dynamic modules.
201
+      if (!$loaded) {
202
+        foreach ($ext['modules'] as $module) {
203
+          if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
204
+            $loaded = true;
205
+            break;
206
+          }
207
+        }
208
+      }
209
+
210
+      if ($loaded) {
211
+        return $ext;
212
+      }
213
+    }
214
+
215
+    return false;
216
+  }
217
+
218
+  /**
219
+   * Returns a singleton instance of the best possible BigMath class.
220
+   * 
221
+   * @return A singleton instance to a BigMath class.
222
+   */
223
+  public static function &getBigMath() {
224
+    if (self::$me == null) {
225
+      $ext = self::BigMath_Detect();
226
+      $class = $ext['class'];
227
+      self::$me = new $class();
228
+    }
229
+
230
+    return self::$me;
231
+  }
232
+
233
+}
234
+
235
+/**
236
+ * Exposes BCmath math library functionality.
237
+ */
238
+class BigMath_BcMathWrapper extends BigMath {
239
+  public function init($number, $base = 10) { return $number; }
240
+
241
+  public function add($x, $y) { return bcadd($x, $y);  }
242
+  public function sub($x, $y) { return bcsub($x, $y);  }
243
+  public function mul($x, $y) { return bcmul($x, $y);  }
244
+  public function div($x, $y) { return bcdiv($x, $y);  }
245
+  public function cmp($x, $y) { return bccomp($x, $y); }
246
+
247
+  public function mod($base, $modulus)    { return bcmod($base, $modulus); }
248
+  public function pow($base, $exponent)   { return bcpow($base, $exponent); }
249
+
250
+  public function powmod($base, $exponent, $modulus) { return bcpowmod($base, $exponent, $modulus); }
251
+
252
+  public function toString($num) { return $num; }
253
+}
254
+
255
+/**
256
+ * Exposes GMP math library functionality.
257
+ */
258
+class BigMath_GmpMathWrapper extends BigMath {
259
+  public function init($number, $base = 10) {  return gmp_init($number, $base); }
260
+
261
+  public function add($x, $y) { return gmp_add($x, $y);   }
262
+  public function sub($x, $y) { return gmp_sub($x, $y);   }
263
+  public function mul($x, $y) { return gmp_mul($x, $y);   }
264
+  public function div($x, $y) { return gmp_div_q($x, $y); }
265
+  public function cmp($x, $y) { return gmp_cmp($x, $y);   }
266
+
267
+  public function mod($base, $modulus)  { return gmp_mod($base, $modulus);  }
268
+  public function pow($base, $exponent) { return gmp_pow($base, $exponent); }
269
+
270
+  public function powmod($base, $exponent, $modulus) { return gmp_powm($base, $exponent, $modulus); }
271
+
272
+  public function toString($num) { return gmp_strval($num); }
273
+}
274
+
275
+?>

+ 190
- 0
discoverer.inc.php View File

@@ -0,0 +1,190 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+class Discoverer {
26
+
27
+ private $server = null;
28
+ private $delegate = '';
29
+ private $identity = '';
30
+ private $version = 1;
31
+
32
+ public function __construct($uri) {
33
+  if ($uri !== null) {
34
+   $this->discover($this->identity = $this->normalise($uri));
35
+  }
36
+ }
37
+
38
+ public function getServer() {
39
+  return $this->server;
40
+ }
41
+
42
+ public function getDelegate() {
43
+  return $this->delegate;
44
+ }
45
+
46
+ public function getIdentity() {
47
+  return $this->identity;
48
+ }
49
+
50
+ public function getVersion() {
51
+  return $this->version;
52
+ }
53
+
54
+ public static function normalise($uri) {
55
+  // Strip xri:// prefix
56
+  if (substr($uri, 0, 6) == 'xri://') {
57
+   $uri = substr($uri, 6);
58
+  }
59
+
60
+  // If the first char is a global context symbol, treat it as XRI
61
+  if (in_array($uri[0], array('=', '@', '+', '$', '!'))) {
62
+   // TODO: Implement
63
+   throw new Exception('This implementation does not currently support XRI');
64
+  }
65
+
66
+  // Add http:// if needed
67
+  if (strpos($uri, '://') === false) {
68
+   $uri = 'http://' . $uri;
69
+  }
70
+
71
+  $bits = @parse_url($uri);
72
+
73
+  $result = $bits['scheme'] . '://';
74
+  if (defined('OPENID_ALLOWUSER') && isset($bits['user'])) {
75
+   $result .= $bits['user'];
76
+   if (isset($bits['pass'])) {
77
+    $result .= ':' . $bits['pass'];
78
+   }
79
+   $result .= '@';
80
+  }
81
+  $result .= preg_replace('/\.$/', '', $bits['host']);
82
+
83
+  if (isset($bits['port']) && !empty($bits['port']) &&
84
+     (($bits['scheme'] == 'http' && $bits['port'] != '80') ||
85
+      ($bits['scheme'] == 'https' && $bits['port'] != '443') ||
86
+      ($bits['scheme'] != 'http' && $bits['scheme'] != 'https'))) {
87
+   $result .= ':' . $bits['port'];
88
+  }
89
+
90
+  if (isset($bits['path'])) {
91
+   do {
92
+    $bits['path'] = preg_replace('#/([^/]*)/\.\./#', '/', str_replace('/./', '/', $old = $bits['path']));
93
+   } while ($old != $bits['path']);
94
+   $result .= $bits['path'];
95
+  } else {
96
+   $result .= '/';
97
+  }
98
+
99
+  if (defined('OPENID_ALLOWQUERY') && isset($bits['query'])) {
100
+   $result .= '?' . $bits['query'];
101
+  }
102
+
103
+  return $result;
104
+ }
105
+
106
+ private function discover($uri) {
107
+  // TODO: Yaris discovery
108
+
109
+  $this->delegate = $uri;
110
+  $this->server = null;
111
+
112
+  $this->htmlDiscover($uri);
113
+ }
114
+
115
+ private function htmlDiscover($uri) {
116
+  $fh = @fopen($uri, 'r');
117
+
118
+  if (!$fh) {
119
+   return;
120
+  }
121
+
122
+  $details = stream_get_meta_data($fh);
123
+
124
+  foreach ($details['wrapper_data'] as $line) {
125
+   if (preg_match('/^Location: (.*?)$/i', $line, $m)) {
126
+    if (strpos($m[1], '://') !== false) {
127
+     // Fully qualified URL
128
+     $this->identity = $m[1];
129
+    } else if ($m[1][0] == '/') {
130
+     // Absolute URL
131
+     $this->identity = preg_replace('#^(.*?://.*?)/.*$#', '\1', $this->identity) . $m[1];
132
+    } else {
133
+     // Relative URL
134
+     $this->identity = preg_replace('#^(.*?://.*/).*?$#', '\1', $this->identity) . $m[1];
135
+    }
136
+   }
137
+   $this->identity = self::normalise($this->identity);
138
+  }
139
+
140
+  $data = '';
141
+  while (!feof($fh) && strpos($data, '</head>') === false) {
142
+   $data .= fgets($fh);
143
+  }
144
+
145
+  fclose($fh);
146
+
147
+  $this->parseHtml($data);
148
+ }
149
+
150
+ public function parseHtml($data) {
151
+  preg_match_all('#<link\s*(.*?)\s*/?>#is', $data, $matches);
152
+
153
+  $links = array();
154
+
155
+  foreach ($matches[1] as $link) {
156
+   $rel = $href = null;
157
+
158
+   if (preg_match('#rel\s*=\s*(?:([^"\'>\s]*)|"([^">]*)"|\'([^\'>]*)\')(?:\s|$)#is', $link, $m)) {
159
+   	array_shift($m);
160
+    $rel = implode('', $m);
161
+   }
162
+
163
+   if (preg_match('#href\s*=\s*(?:([^"\'>\s]*)|"([^">]*)"|\'([^\'>]*)\')(?:\s|$)#is', $link, $m)) {
164
+   	array_shift($m);
165
+    $href = implode('', $m);
166
+   }
167
+
168
+   $links[$rel] = html_entity_decode($href);
169
+  }
170
+
171
+  if (isset($links['openid2.provider'])) {
172
+   $this->version = 2;
173
+   $this->server = $links['openid2.provider'];
174
+
175
+   if (isset($links['openid2.local_id'])) {
176
+    $this->delegate = $links['openid2.local_id'];
177
+   }
178
+  } else if (isset($links['openid.server'])) {
179
+   $this->version = 1;
180
+   $this->server = $links['openid.server'];
181
+
182
+   if (isset($links['openid.delegate'])) {
183
+    $this->delegate = $links['openid.delegate'];
184
+   }
185
+  }
186
+ }
187
+
188
+}
189
+
190
+?>

+ 128
- 0
examples/basic/index.php View File

@@ -0,0 +1,128 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ // Poidsy returns its results in a session variable, so we need to start the
26
+ // session here in order to get access to the results
27
+ session_start();
28
+
29
+ // Request some information from the identity provider. Note that providers 
30
+ // don't have to implement the extension that provides this, so you can't
31
+ // rely on getting results back. You can use OPENID_SREG_REQUEST to more
32
+ // strongly request the information (it implies that the user will have to
33
+ // manually enter the data if the provider doesn't supply it).
34
+ //
35
+ // The fields listed here are all the valid SREG fields. Anything else
36
+ // almost certainly won't work (but you can of course omit ones you don't
37
+ // need).
38
+ define('OPENID_SREG_OPTIONAL',
39
+      'nickname,email,fullname,dob,gender,postcode,country,language,timezone');
40
+
41
+ if (isset($_POST['openid_url']) || isset($_REQUEST['openid_mode'])) {
42
+  // There are two cases when poidsy's processor needs to be invoked - firstly,
43
+  // when the user has just submitted an OpenID identifier to be verified, in
44
+  // which case $_POST['openid_url'] will be present (poidsy has special
45
+  // handling for inputs named openid_url. If you want to use a URL from
46
+  // another source, you can define the OPENID_URL constant instead.).
47
+  // Secondly, if the user is being redirected back from their provider, the
48
+  // openid.mode parameter will be present (which PHP translates to openid_mode)
49
+
50
+  require('../../processor.php');
51
+
52
+ } else {
53
+  // If we don't have any processing to be doing, show them the form and
54
+  // results.
55
+
56
+?>
57
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
58
+                      "http://www.w3.org/TR/html4/strict.dtd">
59
+<html>
60
+ <head>
61
+  <title>OpenID consumer demonstration</title>
62
+  <style type="text/css">
63
+   input#openid_url {
64
+    background: url('../../openid.gif') no-repeat; padding-left: 20px;
65
+   }
66
+
67
+   p { padding-left: 10px; }
68
+   p.error { border-left: 10px solid #f00; }
69
+   p.succ { border-left: 10px solid #0f0; }
70
+   caption { text-align: left; } 
71
+   table { margin: 10px; }
72
+  </style>
73
+ </head>
74
+ <body>
75
+  <h1>OpenID consumer demo</h1>
76
+<?PHP
77
+
78
+ if (isset($_SESSION['openid']['error'])) {
79
+
80
+  // If the error variable is set, it means that poidsy has encountered an
81
+  // error while trying to validate the identifier. We just tell the user
82
+  // what went wrong, and unset the session vars so the messages don't persist
83
+
84
+  echo '<p class="error">An error occured: ', htmlentities($_SESSION['openid']['error']), '</p>';
85
+  unset($_SESSION['openid']['error']);
86
+
87
+ } else if (isset($_SESSION['openid']['validated']) && $_SESSION['openid']['validated']) {
88
+
89
+  // Upon a successful validation, the validated field will have been set to
90
+  // true. It's important to check the validated field, as the identity
91
+  // will be specified in the array throughout the process, so it would be
92
+  // possible for the user to request the page with an identity specified
93
+  // but before Poidsy had validated it. As above, we unset the session
94
+  // vars so that the details don't persist.
95
+
96
+  echo '<p class="succ">Success: your OpenID identifier is <em>', htmlentities($_SESSION['openid']['identity']), '</em></p>';
97
+  unset($_SESSION['openid']['validated']);
98
+
99
+  // Show the SREG data returned, if any. SREG data is only present if you
100
+  // defined one of the OPENID_SREG constants before the request was sent,
101
+  // if the user's identity provider supports SREG, and if (depending on the
102
+  // provider) the user gives permission for you to have the data.
103
+  if (isset($_SESSION['openid']['sreg'])) {
104
+   echo '<table>';
105
+   echo '<caption>Simple Registration Extension data</caption>';
106
+
107
+   foreach ($_SESSION['openid']['sreg'] as $type => $data) {
108
+    echo '<tr><th>', htmlentities($type), '</th>';
109
+    echo '<td>', htmlentities($data), '</td></tr>';
110
+   }
111
+
112
+   echo '</table>';
113
+
114
+   unset($_SESSION['openid']['sreg']);
115
+  }
116
+
117
+ }
118
+?>
119
+  <form action="<?PHP echo htmlentities($_SERVER['REQUEST_URI']); ?>"
120
+	method="post">
121
+   <input type="text" name="openid_url" id="openid_url">
122
+   <input type="submit" value="Login">
123
+  </form>
124
+ </body>
125
+</html>
126
+<?PHP
127
+ }
128
+?>

+ 48
- 0
examples/javascript/iframe.php View File

@@ -0,0 +1,48 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ session_start();
26
+
27
+ define('OPENID_TRUSTROOT', $_SESSION['trustroot']);
28
+ define('OPENID_IMMEDIATE', true);
29
+
30
+ if (isset($_GET['openid_id'])) {
31
+  define('OPENID_URL', $_GET['openid_id']);
32
+ }
33
+
34
+ if (defined('OPENID_URL') || isset($_REQUEST['openid_mode'])) {
35
+
36
+  require('../../processor.php');
37
+
38
+ } else if (isset($_SESSION['openid']['error'])) {
39
+  if ($_SESSION['openid']['errorcode'] == 'noimmediate') {
40
+   echo '<script type="text/javascript">parent.doSubmit();</script>';
41
+  } else {
42
+   echo '<script type="text/javascript">parent.doError("Error: ' . $_SESSION['openid']['error'] . '");</script>';
43
+  }
44
+  unset($_SESSION['openid']['error']);
45
+ } else if (isset($_SESSION['openid']['validated']) && $_SESSION['openid']['validated']) {
46
+  echo '<script type="text/javascript">parent.doSuccess("Logged in as ' . $_SESSION['openid']['identity'] . '");</script>';
47
+ }
48
+?>

+ 132
- 0
examples/javascript/index.php View File

@@ -0,0 +1,132 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ session_start();
26
+
27
+ require('../../urlbuilder.inc.php');
28
+
29
+ if (isset($_GET['cs'])) {
30
+  unset($_SESSION['openid']);
31
+  header('Location: ' . $_SERVER['SCRIPT_NAME']);
32
+  exit;
33
+ }
34
+
35
+ $_SESSION['trustroot'] = URLBuilder::getCurrentURL();
36
+
37
+ if (isset($_POST['openid_url']) || isset($_REQUEST['openid_mode'])) {
38
+  // Proxy for non-JS users
39
+
40
+  require('../../processor.php');
41
+
42
+ } else {
43
+
44
+?>
45
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
46
+                      "http://www.w3.org/TR/html4/strict.dtd">
47
+<html>
48
+ <head>
49
+  <title>OpenID consumer demonstration</title>
50
+  <style type="text/css">
51
+   input#openid_url {
52
+    background: url('../../openid.gif') no-repeat; padding-left: 20px;
53
+   }
54
+   div { margin: 20px; padding: 5px; }
55
+  </style>
56
+  <script type="text/javascript">
57
+   function tryJsLogin() {
58
+    document.getElementById('target').src = 'iframe.php?openid.id=' + document.getElementById('openid_url').value;
59
+   }
60
+   function doSubmit() {
61
+    //alert('Provider is requesting your interaction. Sending you away.');
62
+    document.getElementById('form').submit();
63
+   }
64
+   function doError(msg) {
65
+    document.getElementById('status').innerHTML = msg;
66
+    document.getElementById('status').style.backgroundColor = "#a00";
67
+   }
68
+   function doSuccess(msg) {
69
+    document.getElementById('status').innerHTML = msg;
70
+    document.getElementById('status').style.backgroundColor = "#0a0";
71
+   }
72
+  </script>
73
+ </head>
74
+ <body>
75
+  <h1>OpenID consumer demo</h1>
76
+  <p>
77
+   The login form below uses a hidden iframe to process the form
78
+   (assuming the user has javascript enabled; if they don't, it falls back
79
+   gracefully). If your identity provider implements checkid_immediate
80
+   properly (which several don't appear to), and has enough information to
81
+   authorise you without requiring your input, the entire login process
82
+   should happen without any noticable change except for the status message.
83
+  </p><p>
84
+   If your identity provider requires interaction with you, the form
85
+   will be submitted as usual and you'll leave this page (but, as usual, will
86
+   return when your IdP is done with you). If your identity provider is
87
+   <em>broken</em>, you won't see anything happening after the initial page
88
+   load and redirect. This is because the identity provider is trying to
89
+   interact with you (via a hidden iframe) when it has been explicitly told
90
+   not to. This is the identity provider's fault (it's violating the OpenID
91
+   specifications), not Poidsy's. If you were implementing this on a live site,
92
+   you'd probably add a timer to detect if it wasn't working and do a normal
93
+   login.
94
+  </p>
95
+  <p>
96
+   Note: if you are using Firefox and have the 'Disallow third party cookies'
97
+   preference enabled, Firefox won't send cookies to your provider when it's
98
+   loaded in the iframe. This almost certainly will mean that your provider
99
+   can't validate your identity immediately, and thus you'll be redirected.
100
+   Other browsers (such as IE and Safari) allow these cookies to be sent even
101
+   if they disallow setting of third-party cookies.
102
+  </p>
103
+<?PHP
104
+
105
+ echo '<p>Time: ', date('r'), '. <a href="?cs">Clear session info</a></p>';
106
+
107
+ if (isset($_SESSION['openid']['error'])) {
108
+
109
+  echo '<div id="status" style="background-color: #a00;">An error occured: ', htmlentities($_SESSION['openid']['error']), '</div>';
110
+  unset($_SESSION['openid']['error']);
111
+
112
+ } else if (isset($_SESSION['openid']['validated']) && $_SESSION['openid']['validated']) {
113
+
114
+  echo '<div id="status" style="background-color: #0a0;">Logged in as ', htmlentities($_SESSION['openid']['identity']), '</div>';
115
+
116
+ } else {
117
+
118
+  echo '<div id="status">Not logged in</div>';
119
+
120
+ }
121
+?>
122
+  <form action="<?PHP echo htmlentities($_SERVER['REQUEST_URI']); ?>"
123
+	method="post" onSubmit="tryJsLogin(); return false;" id="form">
124
+   <input type="text" name="openid_url" id="openid_url">
125
+   <input type="submit" value="Login">
126
+   <iframe id="target" style="display: none;"></iframe>
127
+  </form>
128
+ </body>
129
+</html>
130
+<?PHP
131
+ }
132
+?>

+ 1
- 0
keycache.php View File

@@ -0,0 +1 @@
1
+<?PHP header('HTTP/1.1 403 Forbidden'); echo 'This is not a public page'; __halt_compiler(); ?>

+ 362
- 0
keymanager.inc.php View File

@@ -0,0 +1,362 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ require_once(dirname(__FILE__) . '/bigmath.inc.php');
26
+ require_once(dirname(__FILE__) . '/poster.inc.php');
27
+ require_once(dirname(__FILE__) . '/urlbuilder.inc.php');
28
+
29
+ class KeyManager {
30
+
31
+  /** Diffie-Hellman P value, defined by OpenID specification. */
32
+  const DH_P = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
33
+  /** Diffie-Hellman G value. */
34
+  const DH_G = '2';
35
+
36
+  private static $header = null;
37
+  private static $data = null;
38
+  private static $bigmath = null;
39
+
40
+  /**
41
+   * Loads the KeyManager's data array from disk.
42
+   */
43
+  private static function loadData() {
44
+   if (self::$data == null) {
45
+    $data = file(dirname(__FILE__) . '/keycache.php');
46
+    self::$header = array_shift($data);
47
+    self::$data = unserialize(implode("\n", $data));
48
+   }
49
+  }
50
+
51
+  /**
52
+   * Saves the KeyManager's data array to disk.
53
+   */
54
+  private static function saveData() {
55
+   file_put_contents(dirname(__FILE__) . '/keycache.php', self::$header . serialize(self::$data));
56
+  }
57
+
58
+  /**
59
+   * Attempts to associate with the specified server.
60
+   *
61
+   * @param String $server The server to associate with
62
+   */
63
+  public static function associate($server) {
64
+   $data = URLBuilder::buildAssociate($server);
65
+
66
+   try {
67
+    $res = Poster::post($server, $data);
68
+   } catch (Exception $ex) {
69
+    return;
70
+   }
71
+
72
+   $data = array();
73
+
74
+   foreach (explode("\n", $res) as $line) {
75
+    if (preg_match('/^(.*?):(.*)$/', $line, $m)) {
76
+     $data[$m[1]] = $m[2];
77
+    }
78
+   }
79
+
80
+   try {
81
+    $data = self::decodeKey($server, $data);
82
+   } catch (Exception $ex) {
83
+    return;
84
+   }
85
+
86
+   $data['expires_at'] = time() + $data['expires_in'];
87
+
88
+   self::$data[$server][$data['assoc_handle']] = $data;
89
+   self::saveData();
90
+  }
91
+
92
+  /**
93
+   * Decodes the MAC key specified in the $data array.
94
+   *
95
+   * @param String $server The server which sent the data
96
+   * @param Array $data Array of association data from the server
97
+   * @return A copy of the $data array with the mac_key present
98
+   */
99
+  private static function decodeKey($server, $data) {
100
+   switch (strtolower($data['session_type'])) {
101
+    case 'dh-sha1':
102
+     $algo = 'sha1';
103
+     break;
104
+    case 'dh-sha256':
105
+     $algo = 'sha256';
106
+     break;
107
+    case 'no-encryption':
108
+    case 'blank':
109
+    case '':
110
+     $algo = false;
111
+     break;
112
+    default:
113
+     throw new Exception('Unable to handle session type ' . $data['session_type']);
114
+   }
115
+
116
+   if ($algo !== false) {
117
+    // The key is DH'd
118
+    $mac = base64_decode($data['enc_mac_key']);
119
+    $x = self::getDhPrivateKey($server);
120
+    $temp = self::$bigmath->btwoc_undo(base64_decode($data['dh_server_public']));
121
+    $temp = self::$bigmath->powmod($temp, $x, self::DH_P);
122
+    $temp = self::$bigmath->btwoc($temp);
123
+    $temp = hash($algo, $temp, true);
124
+    $mac = $mac ^ $temp;
125
+    $data['mac_key'] = base64_encode($mac);
126
+
127
+    unset($data['enc_mac_key'], $data['dh_server_public']);
128
+   }
129
+
130
+   return $data;
131
+  }
132
+
133
+  /**
134
+   * Retrieves an active assoc_handle for the specified server.
135
+   *
136
+   * @param String $server The server whose handle we're looking for
137
+   * @return An association handle for the server or null on failure
138
+   */
139
+  public static function getHandle($server) {
140
+   self::loadData();
141
+
142
+   if (!isset(self::$data[$server])) {
143
+    return null;
144
+   }
145
+
146
+   foreach (self::$data[$server] as $handle => $data) {
147
+    if ($handle == '__private') { continue; }
148
+
149
+    if ($data['expires_at'] < time()) {
150
+     unset(self::$data[$server][$handle]);
151
+    } else {
152
+     return $handle;
153
+    }
154
+   }
155
+
156
+   return null;
157
+  }
158
+
159
+  /**
160
+   * Determines if the KeyManager has at least one assoc_handle for the
161
+   * specified server.
162
+   *
163
+   * @param String $server The server to check for
164
+   * @return True if the KeyManager has a handle, false otherwise
165
+   */
166
+  public static function hasHandle($server) {
167
+   return self::getHandle($server) !== null;
168
+  }
169
+
170
+  /**
171
+   * Retrieves the association data array for the specified server and assoc
172
+   * handle.
173
+   *
174
+   * @param String $server The server whose data is being requested
175
+   * @param String $handle The current association handle for the server
176
+   * @return Array of association data or null if none was found
177
+   */
178
+  public static function getData($server, $handle) {
179
+   self::loadData();
180
+
181
+   if (isset(self::$data[$server][$handle])) {
182
+    if (self::$data[$server][$handle]['expires_at'] < time()) {
183
+     self::revokeHandle($server, $handle);
184
+     return null;
185
+    } else {
186
+     return self::$data[$server][$handle];
187
+    }
188
+   } else {
189
+    return null;
190
+   }
191
+  }
192
+
193
+  /**
194
+   * Attempts to authenticate that the specified arguments are a valid query
195
+   * from the specified server. If smart authentication is not available
196
+   * or an error is encountered, throws an exception.
197
+   *
198
+   * @param String $server The server that supposedly sent the request
199
+   * @param Array $args The arguments included in the request
200
+   * @return True if the message was authenticated, false if it's a fake
201
+   */
202
+  public static function authenticate($server, $args) {
203
+   $data = self::getData($server, $args['openid_assoc_handle']);
204
+
205
+   if ($data === null) {
206
+    throw new Exception('No key available for that server/handle');
207
+   }
208
+
209
+   $contents = '';
210
+   foreach (explode(',', $args['openid_signed']) as $arg) {
211
+    $argn = str_replace('.', '_', $arg);
212
+    $contents .= $arg . ':' . $args['openid_' . $argn] . "\n";
213
+   }
214
+
215
+   switch (strtolower($data['assoc_type'])) {
216
+    case 'hmac-sha1':
217
+     $algo = 'sha1';
218
+     break;
219
+    case 'hmac-sha256':
220
+     $algo = 'sha256';
221
+     break;
222
+    default:
223
+     throw new Exception('Unable to handle association type ' . $data['assoc_type']);
224
+   }
225
+
226
+   $sig = base64_encode(hash_hmac($algo, $contents, base64_decode($data['mac_key']), true));
227
+
228
+   if ($sig == $args['openid_sig']) {
229
+    return true;
230
+   } else {
231
+    return false;
232
+   }
233
+  }
234
+
235
+  /**
236
+   * Validates the current request using dumb authentication (a POST to the
237
+   * provider).
238
+   *
239
+   * @return True if the request has been authenticated, false otherwise.
240
+   */
241
+  public static function dumbAuthenticate() {
242
+   $url = URLBuilder::buildAuth($_REQUEST);
243
+
244
+   try {
245
+    $data = Poster::post($_SESSION['openid']['server'], $url);
246
+   } catch (Exception $ex) {
247
+    return false;
248
+   }
249
+
250
+   $valid = false;
251
+   foreach (explode("\n", $data) as $line) {
252
+    if (substr($line, 0, 9) == 'is_valid:') {
253
+     $valid = (boolean) substr($line, 9);
254
+    }
255
+   }
256
+
257
+   return $valid;
258
+  }
259
+
260
+  /**
261
+   * Removes the specified association handle from the specified server's
262
+   * records.
263
+   *
264
+   * @param String $server The server which is revoking the handle
265
+   * @param String $handle The handle which is being revoked
266
+   */
267
+  public static function revokeHandle($server, $handle) {
268
+   self::loadData();
269
+   unset(self::$data[$server][$handle]);
270
+   self::saveData();
271
+  }
272
+
273
+  /**
274
+   * Determines if the keymanager is supported by the local environment or not.
275
+   *
276
+   * @return True if the keymanager can be used, false otherwise
277
+   */
278
+  public static function isSupported() {
279
+   return @is_writable(dirname(__FILE__) . '/keycache.php')
280
+	&& function_exists('hash_hmac');
281
+  }
282
+
283
+  /**
284
+   * Returns the base64-encoded representation of the dh_modulus parameter.
285
+   *
286
+   * @return Base64-encoded representation of dh_modulus
287
+   */
288
+  public static function getDhModulus() {
289
+   return base64_encode(self::$bigmath->btwoc(self::DH_P));
290
+  }
291
+
292
+  /**
293
+   * Returns the base64-encoded representation of the dh_gen parameter.
294
+   *
295
+   * @return Base64-encoded representation of dh_gen
296
+   */
297
+  public static function getDhGen() {
298
+   return base64_encode(self::$bigmath->btwoc(self::DH_G));
299
+  }
300
+
301
+  /**
302
+   * Retrieves our private key for the specified server.
303
+   *
304
+   * @param String $server The server which we're communicating with
305
+   * @return Our private key for the specified server, or null if we don't have
306
+   * one
307
+   */
308
+  public static function getDhPrivateKey($server) {
309
+   self::loadData();
310
+   if (isset(self::$data[$server])) {
311
+    return self::$data[$server]['__private'];
312
+   } else {
313
+   	return null;
314
+   }
315
+  }
316
+
317
+  /**
318
+   * Retrieves our public key for use with the specified server.
319
+   *
320
+   * @param String $server The server we wish to send the public key to
321
+   * @return Base64-encoded public key for the specified server
322
+   */
323
+  public static function getDhPublicKey($server) {
324
+   self::loadData();
325
+   $key = self::createDhKey($server);
326
+   self::saveData();
327
+
328
+   return base64_encode(self::$bigmath->btwoc(self::$bigmath->powmod(self::DH_G, $key, self::DH_P)));
329
+  }
330
+
331
+  /**
332
+   * Creates a private key for use when exchanging keys with the specified
333
+   * server.
334
+   *
335
+   * @param String $server The name of the server we're dealing with
336
+   * @return The server's new private key
337
+   */
338
+  private static function createDhKey($server) {
339
+   return self::$data[$server]['__private'] = self::$bigmath->rand(self::DH_P);
340
+  }
341
+
342
+  /**
343
+   * Determines whether the keymanager can support Diffie-Hellman key exchange.
344
+   *
345
+   * @return True if D-H exchange is supported, false otherwise.
346
+   */
347
+  public static function supportsDH() {
348
+   return self::$bigmath != null;
349
+  }
350
+
351
+  /**
352
+   * Initialises the key manager.
353
+   */
354
+  public static function init() {
355
+   self::$bigmath = BigMath::getBigMath();
356
+  }
357
+
358
+ }
359
+
360
+ KeyManager::init();
361
+
362
+?>

BIN
openid.gif View File


+ 53
- 0
poster.inc.php View File

@@ -0,0 +1,53 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ class Poster {
26
+
27
+  public static function post($url, $data) {
28
+   $params = array(
29
+	'http' => array(
30
+		'method' => 'POST',
31
+                 'content' => $data
32
+	)
33
+   );
34
+
35
+   $ctx = stream_context_create($params);
36
+   $fp = @fopen($url, 'rb', false, $ctx);
37
+   
38
+   if (!$fp) {
39
+    throw new Exception("Problem with $url, $php_errormsg");
40
+   }
41
+   
42
+   $response = @stream_get_contents($fp);
43
+   if ($response === false) {
44
+    throw new Exception("Problem reading data from $url, $php_errormsg");
45
+   }
46
+
47
+   return $response;
48
+  }
49
+
50
+
51
+ }
52
+
53
+?>

+ 310
- 0
processor.php View File

@@ -0,0 +1,310 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ // TODO: Remove me before release!
26
+ error_reporting(E_ALL | E_STRICT);
27
+
28
+ require_once(dirname(__FILE__) . '/discoverer.inc.php');
29
+ require_once(dirname(__FILE__) . '/poster.inc.php');
30
+ require_once(dirname(__FILE__) . '/sreg.inc.php');
31
+ require_once(dirname(__FILE__) . '/urlbuilder.inc.php');
32
+ require_once(dirname(__FILE__) . '/keymanager.inc.php');
33
+
34
+ if (session_id() == '') {
35
+  // No session - testing maybe?
36
+  session_start();
37
+ }
38
+
39
+ // Process any openid_url form fields (compatability with 0.1)
40
+ if (!defined('OPENID_URL') && isset($_POST['openid_url'])) {
41
+  define('OPENID_URL', $_POST['openid_url']);
42
+ } else if (!defined('OPENID_URL') && isset($_POST['openid_identifier'])) {
43
+  define('OPENID_URL', $_POST['openid_identifier']);
44
+ }
45
+
46
+ // Maximum number of requests to allow without a OPENID_THROTTLE_GAP second
47
+ // gap between two of them
48
+ if (!defined('OPENID_THROTTLE_NUM')) {
49
+  define('OPENID_THROTTLE_NUM', 3);
50
+ }
51
+
52
+ // Time to require between requests before the request counter is reset
53
+ if (!defined('OPENID_THROTTLE_GAP')) {
54
+  define('OPENID_THROTTLE_GAP', 30);
55
+ }
56
+
57
+ // Whether or not to use the key manager
58
+ define('KEYMANAGER', !defined('OPENID_NOKEYMANAGER') && KeyManager::isSupported());
59
+
60
+ /**
61
+  * Processes the current request.
62
+  */
63
+ function process() {
64
+  if (defined('OPENID_URL')) {
65
+   // Initial authentication attempt (they just entered their identifier)
66
+
67
+   $reqs = checkRequests();
68
+   $disc = tryDiscovery(OPENID_URL);
69
+
70
+   $_SESSION['openid'] = array(
71
+ 	'identity' => $disc->getIdentity(),
72
+	'delegate' => $disc->getDelegate(),
73
+	'validated' => false,
74
+	'server' => $disc->getServer(),
75
+	'nonce' => uniqid(microtime(true), true),
76
+	'requests' => $reqs,
77
+   );
78
+
79
+   $handle = getHandle($disc->getServer());
80
+
81
+   $url = URLBuilder::buildRequest(defined('OPENID_IMMEDIATE') ? 'immediate' : 'setup',
82
+              $disc->getServer(), $disc->getDelegate(),
83
+              $disc->getIdentity(), URLBuilder::getCurrentURL(), $handle);
84
+
85
+   URLBuilder::doRedirect($url);
86
+  } else if (isset($_REQUEST['openid_mode'])) {
87
+   checkNonce();
88
+
89
+   $func = 'process' . str_replace(' ', '', ucwords(str_replace('_', ' ',
90
+			strtolower($_REQUEST['openid_mode']))));
91
+   if (function_exists($func)) {
92
+  	 call_user_func($func, checkHandleRevocation());
93
+   }
94
+  }
95
+ }
96
+
97
+ /**
98
+  * Checks that the user isn't making requests too frequently, and redirects
99
+  * them with an appropriate error if they are.
100
+  *
101
+  * @return An array containing details about the requests that have been made
102
+  */
103
+ function checkRequests() {
104
+  if (isset($_SESSION['openid']['requests'])) {
105
+   $requests = $_SESSION['openid']['requests'];
106
+  } else {
107
+   $requests = array('lasttime' => 0, 'count' => 0);
108
+  }
109
+
110
+  if ($requests['lasttime'] < time() - OPENID_THROTTLE_GAP) {
111
+
112
+   // Last request was a while ago, reset the timer
113
+   $requests['count'] = 0;
114
+
115
+  } else if ($requests['count'] > OPENID_THROTTLE_NUM) {
116
+
117
+   // More than the legal number of requests
118
+   error('throttled', 'You are trying to authenticate too often');
119
+
120
+  }
121
+
122
+  $requests['count']++;
123
+  $requests['lasttime'] = time();
124
+
125
+  return $requests;
126
+ }
127
+
128
+ /**
129
+  * Attempts to perform discovery on the specified URL, redirecting the user
130
+  * with an appropriate error if discovery fails.
131
+  *
132
+  * @param String $url The URL to perform discovery on
133
+  * @return An appropriate Discoverer object
134
+  */
135
+ function tryDiscovery($url) {
136
+  try {
137
+   $disc = new Discoverer($url);
138
+
139
+   if ($disc->getServer() == null) {
140
+   	error('notvalid', 'Claimed identity is not a valid identifier');
141
+   }
142
+
143
+   return $disc;
144
+  } catch (Exception $e) {
145
+   error('discovery', $e->getMessage());
146
+  }
147
+  
148
+  return null;
149
+ }
150
+
151
+ /**
152
+  * Retrieves an association handle for the specified server. If we don't
153
+  * currently have one, attempts to associate with the server.
154
+  *
155
+  * @param String $server The server whose handle we're retrieving
156
+  * @return The association handle of the server or null on failure
157
+  */
158
+ function getHandle($server) {
159
+  if (KEYMANAGER) {
160
+   if (!KeyManager::hasHandle($server)) {
161
+    KeyManager::associate($server);
162
+   }
163
+
164
+   return KeyManager::getHandle($server);
165
+  } else {
166
+   return null;
167
+  }
168
+ }
169
+
170
+ /**
171
+  * Checks that the nonce specified in the current request equals the one
172
+  * stored in the user's session, and redirects them if it doesn't.
173
+  */
174
+ function checkNonce() {
175
+  if ($_REQUEST['openid_nonce'] != $_SESSION['openid']['nonce']) {
176
+   error('nonce', 'Nonce doesn\'t match - possible replay attack');
177
+  } else {
178
+   $_SESSION['openid']['nonce'] = uniqid(microtime(true), true);
179
+  }
180
+ }
181
+
182
+ /**
183
+  * Checks to see if the request contains an instruction to invalidate the
184
+  * handle we used. If it does, the request is authenticated and the handle
185
+  * removed (or the user is redirected with an error if the IdP doesn't
186
+  * authenticate the message).
187
+  *
188
+  * @return True if the message has been authenticated, false otherwise
189
+  */
190
+ function checkHandleRevocation() {
191
+  $valid = false;
192
+
193
+  if (KEYMANAGER && isset($_REQUEST['openid_invalidate_handle'])) {
194
+   $valid = KeyManager::dumbAuth();
195
+
196
+   if ($valid) {
197
+    KeyManager::removeKey($_SESSION['openid']['server'], $_REQUEST['openid_invalidate_handle']);
198
+   } else {
199
+   	error('noauth', 'Provider didn\'t authenticate message');
200
+   }
201
+  }
202
+
203
+  return $valid;
204
+ }
205
+
206
+ /**
207
+  * Processes id_res requests.
208
+  *
209
+  * @param Boolean $valid True if the request has already been authenticated
210
+  */
211
+ function processIdRes($valid) {
212
+  if (isset($_REQUEST['openid_identity'])) {
213
+   processPositiveResponse($valid);
214
+  } else if (isset($_REQUEST['openid_user_setup_url'])) {
215
+   processSetupRequest();
216
+  }
217
+ }
218
+ 
219
+ /**
220
+  * Processes a response where the provider is requesting to interact with the
221
+  * user in order to confirm their identity.
222
+  */
223
+ function processSetupRequest() {
224
+  if (defined('OPENID_IMMEDIATE') && OPENID_IMMEDIATE) {
225
+   error('noimmediate', 'Couldn\'t perform immediate auth');
226
+  }
227
+
228
+  $handle = getHandle($_SESSION['openid']['server']);
229
+
230
+  $url = URLBuilder::buildRequest('setup', $_REQUEST['openid_user_setup_url'],
231
+                                $_SESSION['openid']['delegate'],
232
+                                $_SESSION['openid']['identity'],
233
+                                URLBuilder::getCurrentURL(), $handle);
234
+
235
+  URLBuilder::doRedirect($url); 	
236
+ }
237
+ 
238
+ /**
239
+  * Processes a positive authentication response.
240
+  *
241
+  * @param Boolean $valid True if the request has already been authenticated
242
+  */
243
+ function processPositiveResponse($valid) {
244
+  if ($_REQUEST['openid_identity'] != $_SESSION['openid']['delegate']) {
245
+   error('diffid', 'Identity provider validated wrong identity. Expected it to '
246
+  	             . 'validate ' . $_SESSION['openid']['delegate'] . ' but it '
247
+  	             . 'validated ' . $_REQUEST['openid_identity']);
248
+  }
249
+
250
+  if (!$valid) {
251
+   $dumbauth = true;
252
+
253
+   if (KEYMANAGER) {
254
+    try {
255
+     $valid = KeyManager::authenticate($_SESSION['openid']['server'], $_REQUEST);
256
+     $dumbauth = false;
257
+    } catch (Exception $ex) {
258
+     // Ignore it - try dumb auth
259
+    }
260
+   }
261
+
262
+   if ($dumbauth) {
263
+    $valid = KeyManager::dumbAuthenticate();
264
+   }
265
+  }
266
+
267
+  $_SESSION['openid']['validated'] = $valid;
268
+
269
+  if (!$valid) {
270
+  	error('noauth', 'Provider didn\'t authenticate response');
271
+  }
272
+
273
+  parseSRegResponse();
274
+  URLBuilder::redirect(); 	
275
+ }
276
+
277
+ /**
278
+  * Processes cancel modes.
279
+  *
280
+  * @param Boolean $valid True if the request has already been authenticated
281
+  */
282
+ function processCancel($valid) {
283
+  error('cancelled', 'Provider cancelled the authentication attempt');
284
+ }
285
+
286
+ /**
287
+  * Processes error modes.
288
+  *
289
+  * @param Boolean $valid True if the request has already been authenticated
290
+  */
291
+ function processError($valid) {
292
+  error('perror', 'Provider error: ' . $_REQUEST['openid_error']);
293
+ }
294
+
295
+ /**
296
+  * Populates the session array with the details of the specified error and
297
+  * redirects the user appropriately.
298
+  *
299
+  * @param String $code The error code that occured
300
+  * @param String $message A description of the error
301
+  */
302
+ function error($code, $message) {
303
+  $_SESSION['openid']['error'] = $message;
304
+  $_SESSION['openid']['errorcode'] = $code;
305
+  URLBuilder::redirect();
306
+ }
307
+
308
+ // Here we go!
309
+ process();
310
+?>

+ 48
- 0
sreg.inc.php View File

@@ -0,0 +1,48 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ define('SREG_NICKNAME', 'openid.sreg.nickname');
26
+ define('SREG_EMAIL', 'openid.sreg.email');
27
+ define('SREG_FULLNAME', 'openid.sreg.fullname');
28
+ define('SREG_DOB', 'openid.sreg.dob');
29
+ define('SREG_GENDER', 'openid.sreg.gender');
30
+ define('SREG_POSTCODE', 'openid.sreg.postcode');
31
+ define('SREG_COUNTRY', 'openid.sreg.country');
32
+ define('SREG_LANGUAGE', 'openid.sreg.language');
33
+ define('SREG_TIMEZONE', 'openid.sreg.timezone');
34
+
35
+ define('SREG_ALL', SREG_NICKNAME . ',' . SREG_EMAIL . ',' . SREG_FULLNAME
36
+             . ',' . SREG_DOB . ',' . SREG_GENDER . ',' . SREG_POSTCODE . ','
37
+             . SREG_COUNTRY . ',' . SREG_LANGUAGE . ', ' . SREG_TIMEZONE);
38
+
39
+ function parseSRegResponse() {
40
+  foreach (explode(',', SREG_ALL) as $reg) {
41
+   $reg = str_replace('.', '_', $reg);
42
+   if (isset($_REQUEST[$reg])) {
43
+    $_SESSION['openid']['sreg'][substr($reg, 12)] = $_REQUEST[$reg];
44
+   }
45
+  }
46
+ }
47
+
48
+?>

+ 106
- 0
test.php View File

@@ -0,0 +1,106 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ // This file tests to see if your environment is compatible with Poidsy.
26
+
27
+?>
28
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
29
+                      "http://www.w3.org/TR/html4/strict.dtd">
30
+<html>
31
+ <head>
32
+  <title>Poidsy compatibility test</title>
33
+  <style type="text/css">
34
+   table { border-collapse: collapse; }
35
+   td, th { border: 1px solid #000; padding: 10px; }
36
+   td.error { text-align: center; color: #fff; background-color: #c00; }
37
+   td.succ { text-align: center; color: #fff; background-color: #0c0; }
38
+   td.warn { text-align: center; color: #fff; background-color: #c70; }
39
+   td { max-width: 400px; }
40
+   code { font-size: small; }
41
+  </style>
42
+ </head>
43
+ <body>
44
+  <h1>Poidsy compatibility test</h1>
45
+  <table>
46
+<?PHP
47
+
48
+ function doTest($name, $result, $failinfo) {
49
+  echo '<tr><th>', htmlentities($name), '</th>';
50
+  echo '<td class="', $result ? 'succ' : 'error', '">';
51
+  echo $result ? 'Passed' : 'Failed';
52
+  echo '</td>';
53
+  if (!$result) { echo '<td>', $failinfo, '</td>'; }
54
+  echo '</tr>';
55
+ }
56
+
57
+ echo '<tr><th colspan="2">Poidsy requirements</th></tr>';
58
+ doTest('PHP Version', version_compare(PHP_VERSION, '5.2.0', '>='), 'Poidsy requires PHP version 5.2.0 or greater to run');
59
+
60
+ echo '<tr><th colspan="2">Associate mode requirements</th></tr>';
61
+ doTest('hash_hmac function', function_exists('hash_hmac'), 'Poidsy requires the hash_hmac function to use associate mode. It should be available in PHP 5.2.0 or greater, unless you\'ve explicitly disabled it when compiling PHP');
62
+ doTest('Keycache writable', is_writable(dirname(__FILE__) . '/keycache.php'), 'Poidsy requires write access to the keycache.php file in its directory. Without it, Poidsy will be unable to use associate mode.');
63
+ echo '<tr><th colspan="2">Diffie-Hellman key exchange requirements</th></tr>';
64
+
65
+ $extensions = array(
66
+        array('modules' => array('gmp', 'php_gmp'),
67
+              'extension' => 'gmp'),
68
+        array('modules' => array('bcmath', 'php_bcmath'),
69
+              'extension' => 'bcmath')
70
+    );
71
+
72
+ $best = '';
73
+ foreach ($extensions as $ext) {
74
+  if ($ext['extension'] && extension_loaded($ext['extension'])) {
75
+   $loaded = true;
76
+  } else {
77
+   foreach ($ext['modules'] as $module) {
78
+    if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
79
+     $loaded = true;
80
+     break;
81
+    }
82
+   }
83
+  }
84
+
85
+  if ($loaded) {
86
+   $best = $ext['extension'];
87
+   break; 
88
+  }
89
+ }
90
+
91
+  echo '<tr><th>Bigmath support</th>';
92
+  echo '<td class="', $best == 'gmp' ? 'succ' : $best == 'bcmath' ? 'warn' : 'error', '">';
93
+  echo $best != '' ? $best : 'Failed';
94
+  echo '</td>';
95
+  if ($best == 'bcmath') {
96
+   echo '<td>Your version of PHP has bcmath support, which is good enough for Poidsy to use, but is much slower than gmp.</td>';
97
+  } else if ($best == '') {
98
+   echo '<td>Your version of PHP doesn\'t have support for either gmp (preferred) or bcmath. Poidsy needs one of these libraries to perform D-H key exchange.</td>';
99
+  }
100
+  echo '</tr>';
101
+
102
+
103
+?>
104
+  </table>
105
+ </body>
106
+</html>

+ 189
- 0
urlbuilder.inc.php View File

@@ -0,0 +1,189 @@
1
+<?PHP
2
+
3
+/* Poidsy 0.4 - http://chris.smith.name/projects/poidsy
4
+ * Copyright (c) 2008 Chris Smith
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in
14
+ * all copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ require_once(dirname(__FILE__) . '/keymanager.inc.php');
26
+
27
+ class URLBuilder {
28
+
29
+  const NAMESPACE = 'http://openid.net/signon/1.1';
30
+
31
+  public static function addArguments($base, $arguments) {
32
+   $first = true;
33
+   $res = $base === false ? '' : $base;
34
+
35
+   if ($base !== false && strrpos($base, '?', -2) === false) {
36
+    if ($base[strlen($base) - 1] != '?') {
37
+     $res .= '?';
38
+    }
39
+   } else if ($base !== false) {
40
+    $res .= '&';
41
+   }
42
+
43
+   foreach ($arguments as $key => $value) {
44
+    if ($first) {
45
+     $first = false;
46
+    } else {
47
+     $res .= '&';
48
+    }
49
+
50
+    $res .= urlencode($key) . '=' . urlencode($value);
51
+   }
52
+
53
+   return $res;
54
+  }
55
+
56
+  public static function buildRequest($type, $base, $delegate, $identity, $returnURL, $handle) {
57
+   $args = array(
58
+    'openid.ns' => self::NAMESPACE,
59
+	'openid.mode' => 'checkid_' . $type,
60
+	'openid.identity' => $delegate,
61
+	'openid.claimed_id' => $identity,
62
+	'openid.trust_root' => self::getTrustRoot(),
63
+	'openid.return_to' => self::addArguments($returnURL,
64
+		array('openid.nonce' => $_SESSION['openid']['nonce']))
65
+   );
66
+
67
+   if ($handle !== null) {
68
+    $args['openid.assoc_handle'] = $handle;
69
+   }
70
+
71
+   self::addSRegArgs($args);
72
+
73
+   return self::addArguments($base, $args);
74
+  }
75
+
76
+  private static function getTrustRoot() {
77
+   if (defined('OPENID_TRUSTROOT')) {
78
+    return OPENID_TRUSTROOT;
79
+   } else {
80
+    return self::getCurrentURL();
81
+   }
82
+  }
83
+
84
+  private static function addSRegArgs(&$args) {
85
+   if (defined('OPENID_SREG_REQUEST')) {
86
+    $args['openid.sreg.required'] = OPENID_SREG_REQUEST;
87
+   }
88
+
89
+   if (defined('OPENID_SREG_OPTIONAL')) {
90
+    $args['openid.sreg.optional'] = OPENID_SREG_OPTIONAL;
91
+   }
92
+
93
+   if (defined('OPENID_SREG_POLICY')) {
94
+    $args['openid.sreg.policy_url'] = OPENID_SREG_POLICY;
95
+   }
96
+  }
97
+
98
+  public static function buildAssociate($server) {
99
+   $args = array(
100
+        'openid.ns' => self::NAMESPACE,
101
+	'openid.mode' => 'associate',
102
+	'openid.assoc_type' => 'HMAC-SHA1',
103
+   );
104
+
105
+   if (KeyManager::supportsDH()) {
106
+    $args['openid.session_type'] = 'DH-SHA1';
107
+    $args['openid.dh_modulus'] = KeyManager::getDhModulus();
108
+    $args['openid.dh_gen'] = KeyManager::getDhGen();
109
+    $args['openid.dh_consumer_public'] = KeyManager::getDhPublicKey($server);
110
+   } else {
111
+    $args['openid.session_type'] = '';
112
+   }
113
+
114
+   return self::addArguments(false, $args);
115
+  }
116
+
117
+  public static function buildAuth($params) {
118
+   $args = array(
119
+        'openid.ns' => self::NAMESPACE,
120
+	'openid.mode' => 'check_authentication'
121
+   );
122
+
123
+   $toadd = array('assoc_handle', 'sig', 'signed');
124
+   $toadd = array_merge($toadd, explode(',', $params['openid_signed']));
125
+
126
+   foreach ($toadd as $arg) {
127
+    if (!isset($args['openid.' . $arg])) {
128
+     $args['openid.' . $arg] = $params['openid_' . $arg];
129
+    }
130
+   }
131
+
132
+   return self::addArguments(false, $args);
133
+  }
134
+
135
+  public static function getCurrentURL() {
136
+   $res = 'http';
137
+
138
+   if (isset($_SERVER['HTTPS'])) {
139
+    $res = 'https';
140
+   }
141
+
142
+   $res .= '://' . $_SERVER['SERVER_NAME'];
143
+
144
+   if ($_SERVER['SERVER_PORT'] != 80) {
145
+    $res .= ':' . $_SERVER['SERVER_PORT'];
146
+   }
147
+
148
+   $url = $_SERVER['REQUEST_URI'];
149
+
150
+   while (preg_match('/([\?&])openid[\._](.*?)=(.*?)(&|$)/', $url, $m)) {
151
+    $url = str_replace($m[0], $m[1], $url);
152
+   }
153
+
154
+   $url = preg_replace('/\??&*$/', '', $url);
155
+
156
+   return $res . $url;
157
+  }
158
+
159
+  /**
160
+   * Redirects the user back to their original page.
161
+   */
162
+  public static function redirect() {
163
+   if (defined('OPENID_REDIRECTURL')) {
164
+    $url = OPENID_REDIRECTURL;
165
+   } else if (isset($_SESSION['openid']['redirect'])) {
166
+    $url = $_SESSION['openid']['redirect'];
167
+   } else {
168
+    $url = self::getCurrentURL();
169
+   }
170
+
171
+   self::doRedirect($url);
172
+  }
173
+
174
+  /**
175
+   * Redirects the user to the specified URL.
176
+   *
177
+   * @param $url The URL to redirect the user to
178
+   */
179
+  public static function doRedirect($url) {
180
+   header('Location: ' . $url);
181
+   echo '<html><head><title>Redirecting</title></head><body>';
182
+   echo '<p>Redirecting to <a href="', htmlentities($url), '">';
183
+   echo htmlentities($url), '</a></p></body></html>';
184
+   exit();
185
+  }
186
+
187
+ }
188
+
189
+?>

Loading…
Cancel
Save