1 | <?php␊ |
2 | /**␊ |
3 | * Copyright 2011 Facebook, Inc.␊ |
4 | *␊ |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may␊ |
6 | * not use this file except in compliance with the License. You may obtain␊ |
7 | * a copy of the License at␊ |
8 | *␊ |
9 | * http://www.apache.org/licenses/LICENSE-2.0␊ |
10 | *␊ |
11 | * Unless required by applicable law or agreed to in writing, software␊ |
12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT␊ |
13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the␊ |
14 | * License for the specific language governing permissions and limitations␊ |
15 | * under the License.␊ |
16 | */␊ |
17 | ␊ |
18 | require_once "base_facebook.php";␊ |
19 | ␊ |
20 | /**␊ |
21 | * Extends the BaseFacebook class with the intent of using␊ |
22 | * PHP sessions to store user ids and access tokens.␊ |
23 | */␊ |
24 | class Facebook extends BaseFacebook␊ |
25 | {␊ |
26 | /**␊ |
27 | * Cookie prefix␊ |
28 | */␊ |
29 | const FBSS_COOKIE_NAME = 'fbss';␊ |
30 | ␊ |
31 | /**␊ |
32 | * We can set this to a high number because the main session␊ |
33 | * expiration will trump this.␊ |
34 | */␊ |
35 | const FBSS_COOKIE_EXPIRE = 31556926; // 1 year␊ |
36 | ␊ |
37 | /**␊ |
38 | * Stores the shared session ID if one is set.␊ |
39 | *␊ |
40 | * @var string␊ |
41 | */␊ |
42 | protected $sharedSessionID;␊ |
43 | ␊ |
44 | /**␊ |
45 | * Identical to the parent constructor, except that␊ |
46 | * we start a PHP session to store the user ID and␊ |
47 | * access token if during the course of execution␊ |
48 | * we discover them.␊ |
49 | *␊ |
50 | * @param array $config the application configuration. Additionally␊ |
51 | * accepts "sharedSession" as a boolean to turn on a secondary␊ |
52 | * cookie for environments with a shared session (that is, your app␊ |
53 | * shares the domain with other apps).␊ |
54 | *␊ |
55 | * @see BaseFacebook::__construct␊ |
56 | */␊ |
57 | public function __construct($config) {␊ |
58 | if ((function_exists('session_status') ␊ |
59 | && session_status() !== PHP_SESSION_ACTIVE) || !session_id()) {␊ |
60 | session_start();␊ |
61 | }␊ |
62 | parent::__construct($config);␊ |
63 | if (!empty($config['sharedSession'])) {␊ |
64 | $this->initSharedSession();␊ |
65 | ␊ |
66 | // re-load the persisted state, since parent␊ |
67 | // attempted to read out of non-shared cookie␊ |
68 | $state = $this->getPersistentData('state');␊ |
69 | if (!empty($state)) {␊ |
70 | $this->state = $state;␊ |
71 | } else {␊ |
72 | $this->state = null;␊ |
73 | }␊ |
74 | ␊ |
75 | }␊ |
76 | }␊ |
77 | ␊ |
78 | /**␊ |
79 | * Supported keys for persistent data␊ |
80 | *␊ |
81 | * @var array␊ |
82 | */␊ |
83 | protected static $kSupportedKeys =␊ |
84 | array('state', 'code', 'access_token', 'user_id');␊ |
85 | ␊ |
86 | /**␊ |
87 | * Initiates Shared Session␊ |
88 | */␊ |
89 | protected function initSharedSession() {␊ |
90 | $cookie_name = $this->getSharedSessionCookieName();␊ |
91 | if (isset($_COOKIE[$cookie_name])) {␊ |
92 | $data = $this->parseSignedRequest($_COOKIE[$cookie_name]);␊ |
93 | if ($data && !empty($data['domain']) &&␊ |
94 | self::isAllowedDomain($this->getHttpHost(), $data['domain'])) {␊ |
95 | // good case␊ |
96 | $this->sharedSessionID = $data['id'];␊ |
97 | return;␊ |
98 | }␊ |
99 | // ignoring potentially unreachable data␊ |
100 | }␊ |
101 | // evil/corrupt/missing case␊ |
102 | $base_domain = $this->getBaseDomain();␊ |
103 | $this->sharedSessionID = md5(uniqid(mt_rand(), true));␊ |
104 | $cookie_value = $this->makeSignedRequest(␊ |
105 | array(␊ |
106 | 'domain' => $base_domain,␊ |
107 | 'id' => $this->sharedSessionID,␊ |
108 | )␊ |
109 | );␊ |
110 | $_COOKIE[$cookie_name] = $cookie_value;␊ |
111 | if (!headers_sent()) {␊ |
112 | $expire = time() + self::FBSS_COOKIE_EXPIRE;␊ |
113 | setcookie($cookie_name, $cookie_value, $expire, '/', '.'.$base_domain);␊ |
114 | } else {␊ |
115 | // @codeCoverageIgnoreStart␊ |
116 | self::errorLog(␊ |
117 | 'Shared session ID cookie could not be set! You must ensure you '.␊ |
118 | 'create the Facebook instance before headers have been sent. This '.␊ |
119 | 'will cause authentication issues after the first request.'␊ |
120 | );␊ |
121 | // @codeCoverageIgnoreEnd␊ |
122 | }␊ |
123 | }␊ |
124 | ␊ |
125 | /**␊ |
126 | * Provides the implementations of the inherited abstract␊ |
127 | * methods. The implementation uses PHP sessions to maintain␊ |
128 | * a store for authorization codes, user ids, CSRF states, and␊ |
129 | * access tokens.␊ |
130 | */␊ |
131 | ␊ |
132 | /**␊ |
133 | * {@inheritdoc}␊ |
134 | *␊ |
135 | * @see BaseFacebook::setPersistentData()␊ |
136 | */␊ |
137 | protected function setPersistentData($key, $value) {␊ |
138 | if (!in_array($key, self::$kSupportedKeys)) {␊ |
139 | self::errorLog('Unsupported key passed to setPersistentData.');␊ |
140 | return;␊ |
141 | }␊ |
142 | ␊ |
143 | $session_var_name = $this->constructSessionVariableName($key);␊ |
144 | $_SESSION[$session_var_name] = $value;␊ |
145 | }␊ |
146 | ␊ |
147 | /**␊ |
148 | * {@inheritdoc}␊ |
149 | *␊ |
150 | * @see BaseFacebook::getPersistentData()␊ |
151 | */␊ |
152 | protected function getPersistentData($key, $default = false) {␊ |
153 | if (!in_array($key, self::$kSupportedKeys)) {␊ |
154 | self::errorLog('Unsupported key passed to getPersistentData.');␊ |
155 | return $default;␊ |
156 | }␊ |
157 | ␊ |
158 | $session_var_name = $this->constructSessionVariableName($key);␊ |
159 | return isset($_SESSION[$session_var_name]) ?␊ |
160 | $_SESSION[$session_var_name] : $default;␊ |
161 | }␊ |
162 | ␊ |
163 | /**␊ |
164 | * {@inheritdoc}␊ |
165 | *␊ |
166 | * @see BaseFacebook::clearPersistentData()␊ |
167 | */␊ |
168 | protected function clearPersistentData($key) {␊ |
169 | if (!in_array($key, self::$kSupportedKeys)) {␊ |
170 | self::errorLog('Unsupported key passed to clearPersistentData.');␊ |
171 | return;␊ |
172 | }␊ |
173 | ␊ |
174 | $session_var_name = $this->constructSessionVariableName($key);␊ |
175 | if (isset($_SESSION[$session_var_name])) {␊ |
176 | unset($_SESSION[$session_var_name]);␊ |
177 | }␊ |
178 | }␊ |
179 | ␊ |
180 | /**␊ |
181 | * {@inheritdoc}␊ |
182 | *␊ |
183 | * @see BaseFacebook::clearAllPersistentData()␊ |
184 | */␊ |
185 | protected function clearAllPersistentData() {␊ |
186 | foreach (self::$kSupportedKeys as $key) {␊ |
187 | $this->clearPersistentData($key);␊ |
188 | }␊ |
189 | if ($this->sharedSessionID) {␊ |
190 | $this->deleteSharedSessionCookie();␊ |
191 | }␊ |
192 | }␊ |
193 | ␊ |
194 | /**␊ |
195 | * Deletes Shared session cookie␊ |
196 | */␊ |
197 | protected function deleteSharedSessionCookie() {␊ |
198 | $cookie_name = $this->getSharedSessionCookieName();␊ |
199 | unset($_COOKIE[$cookie_name]);␊ |
200 | $base_domain = $this->getBaseDomain();␊ |
201 | setcookie($cookie_name, '', 1, '/', '.'.$base_domain);␊ |
202 | }␊ |
203 | ␊ |
204 | /**␊ |
205 | * Returns the Shared session cookie name␊ |
206 | *␊ |
207 | * @return string The Shared session cookie name␊ |
208 | */␊ |
209 | protected function getSharedSessionCookieName() {␊ |
210 | return self::FBSS_COOKIE_NAME . '_' . $this->getAppId();␊ |
211 | }␊ |
212 | ␊ |
213 | /**␊ |
214 | * Constructs and returns the name of the session key.␊ |
215 | *␊ |
216 | * @see setPersistentData()␊ |
217 | * @param string $key The key for which the session variable name to construct.␊ |
218 | *␊ |
219 | * @return string The name of the session key.␊ |
220 | */␊ |
221 | protected function constructSessionVariableName($key) {␊ |
222 | $parts = array('fb', $this->getAppId(), $key);␊ |
223 | if ($this->sharedSessionID) {␊ |
224 | array_unshift($parts, $this->sharedSessionID);␊ |
225 | }␊ |
226 | return implode('_', $parts);␊ |
227 | }␊ |
228 | }␊ |