Passed
Push — master ( 06f0da...dd891e )
by Julius
15:40 queued 14s
created
apps/dav/lib/Connector/Sabre/Auth.php 1 patch
Indentation   +182 added lines, -182 removed lines patch added patch discarded remove patch
@@ -49,186 +49,186 @@
 block discarded – undo
49 49
 use Sabre\HTTP\ResponseInterface;
50 50
 
51 51
 class Auth extends AbstractBasic {
52
-	public const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
53
-
54
-	private ISession $session;
55
-	private Session $userSession;
56
-	private IRequest $request;
57
-	private ?string $currentUser = null;
58
-	private Manager $twoFactorManager;
59
-	private Throttler $throttler;
60
-
61
-	public function __construct(ISession $session,
62
-								Session $userSession,
63
-								IRequest $request,
64
-								Manager $twoFactorManager,
65
-								Throttler $throttler,
66
-								string $principalPrefix = 'principals/users/') {
67
-		$this->session = $session;
68
-		$this->userSession = $userSession;
69
-		$this->twoFactorManager = $twoFactorManager;
70
-		$this->request = $request;
71
-		$this->throttler = $throttler;
72
-		$this->principalPrefix = $principalPrefix;
73
-
74
-		// setup realm
75
-		$defaults = new \OCP\Defaults();
76
-		$this->realm = $defaults->getName();
77
-	}
78
-
79
-	/**
80
-	 * Whether the user has initially authenticated via DAV
81
-	 *
82
-	 * This is required for WebDAV clients that resent the cookies even when the
83
-	 * account was changed.
84
-	 *
85
-	 * @see https://github.com/owncloud/core/issues/13245
86
-	 */
87
-	public function isDavAuthenticated(string $username): bool {
88
-		return !is_null($this->session->get(self::DAV_AUTHENTICATED)) &&
89
-		$this->session->get(self::DAV_AUTHENTICATED) === $username;
90
-	}
91
-
92
-	/**
93
-	 * Validates a username and password
94
-	 *
95
-	 * This method should return true or false depending on if login
96
-	 * succeeded.
97
-	 *
98
-	 * @param string $username
99
-	 * @param string $password
100
-	 * @return bool
101
-	 * @throws PasswordLoginForbidden
102
-	 */
103
-	protected function validateUserPass($username, $password) {
104
-		if ($this->userSession->isLoggedIn() &&
105
-			$this->isDavAuthenticated($this->userSession->getUser()->getUID())
106
-		) {
107
-			$this->session->close();
108
-			return true;
109
-		} else {
110
-			try {
111
-				if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) {
112
-					$this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
113
-					$this->session->close();
114
-					return true;
115
-				} else {
116
-					$this->session->close();
117
-					return false;
118
-				}
119
-			} catch (PasswordLoginForbiddenException $ex) {
120
-				$this->session->close();
121
-				throw new PasswordLoginForbidden();
122
-			}
123
-		}
124
-	}
125
-
126
-	/**
127
-	 * @return array{bool, string}
128
-	 * @throws NotAuthenticated
129
-	 * @throws ServiceUnavailable
130
-	 */
131
-	public function check(RequestInterface $request, ResponseInterface $response) {
132
-		try {
133
-			return $this->auth($request, $response);
134
-		} catch (NotAuthenticated $e) {
135
-			throw $e;
136
-		} catch (Exception $e) {
137
-			$class = get_class($e);
138
-			$msg = $e->getMessage();
139
-			\OC::$server->get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
140
-			throw new ServiceUnavailable("$class: $msg");
141
-		}
142
-	}
143
-
144
-	/**
145
-	 * Checks whether a CSRF check is required on the request
146
-	 */
147
-	private function requiresCSRFCheck(): bool {
148
-		// GET requires no check at all
149
-		if ($this->request->getMethod() === 'GET') {
150
-			return false;
151
-		}
152
-
153
-		// Official Nextcloud clients require no checks
154
-		if ($this->request->isUserAgent([
155
-			IRequest::USER_AGENT_CLIENT_DESKTOP,
156
-			IRequest::USER_AGENT_CLIENT_ANDROID,
157
-			IRequest::USER_AGENT_CLIENT_IOS,
158
-		])) {
159
-			return false;
160
-		}
161
-
162
-		// If not logged-in no check is required
163
-		if (!$this->userSession->isLoggedIn()) {
164
-			return false;
165
-		}
166
-
167
-		// POST always requires a check
168
-		if ($this->request->getMethod() === 'POST') {
169
-			return true;
170
-		}
171
-
172
-		// If logged-in AND DAV authenticated no check is required
173
-		if ($this->userSession->isLoggedIn() &&
174
-			$this->isDavAuthenticated($this->userSession->getUser()->getUID())) {
175
-			return false;
176
-		}
177
-
178
-		return true;
179
-	}
180
-
181
-	/**
182
-	 * @return array{bool, string}
183
-	 * @throws NotAuthenticated
184
-	 */
185
-	private function auth(RequestInterface $request, ResponseInterface $response): array {
186
-		$forcedLogout = false;
187
-
188
-		if (!$this->request->passesCSRFCheck() &&
189
-			$this->requiresCSRFCheck()) {
190
-			// In case of a fail with POST we need to recheck the credentials
191
-			if ($this->request->getMethod() === 'POST') {
192
-				$forcedLogout = true;
193
-			} else {
194
-				$response->setStatus(401);
195
-				throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
196
-			}
197
-		}
198
-
199
-		if ($forcedLogout) {
200
-			$this->userSession->logout();
201
-		} else {
202
-			if ($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
203
-				throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
204
-			}
205
-			if (
206
-				//Fix for broken webdav clients
207
-				($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) ||
208
-				//Well behaved clients that only send the cookie are allowed
209
-				($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ||
210
-				\OC_User::handleApacheAuth()
211
-			) {
212
-				$user = $this->userSession->getUser()->getUID();
213
-				$this->currentUser = $user;
214
-				$this->session->close();
215
-				return [true, $this->principalPrefix . $user];
216
-			}
217
-		}
218
-
219
-		if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {
220
-			// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
221
-			$response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"');
222
-			$response->setStatus(401);
223
-			throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
224
-		}
225
-
226
-		$data = parent::check($request, $response);
227
-		if ($data[0] === true) {
228
-			$startPos = strrpos($data[1], '/') + 1;
229
-			$user = $this->userSession->getUser()->getUID();
230
-			$data[1] = substr_replace($data[1], $user, $startPos);
231
-		}
232
-		return $data;
233
-	}
52
+    public const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND';
53
+
54
+    private ISession $session;
55
+    private Session $userSession;
56
+    private IRequest $request;
57
+    private ?string $currentUser = null;
58
+    private Manager $twoFactorManager;
59
+    private Throttler $throttler;
60
+
61
+    public function __construct(ISession $session,
62
+                                Session $userSession,
63
+                                IRequest $request,
64
+                                Manager $twoFactorManager,
65
+                                Throttler $throttler,
66
+                                string $principalPrefix = 'principals/users/') {
67
+        $this->session = $session;
68
+        $this->userSession = $userSession;
69
+        $this->twoFactorManager = $twoFactorManager;
70
+        $this->request = $request;
71
+        $this->throttler = $throttler;
72
+        $this->principalPrefix = $principalPrefix;
73
+
74
+        // setup realm
75
+        $defaults = new \OCP\Defaults();
76
+        $this->realm = $defaults->getName();
77
+    }
78
+
79
+    /**
80
+     * Whether the user has initially authenticated via DAV
81
+     *
82
+     * This is required for WebDAV clients that resent the cookies even when the
83
+     * account was changed.
84
+     *
85
+     * @see https://github.com/owncloud/core/issues/13245
86
+     */
87
+    public function isDavAuthenticated(string $username): bool {
88
+        return !is_null($this->session->get(self::DAV_AUTHENTICATED)) &&
89
+        $this->session->get(self::DAV_AUTHENTICATED) === $username;
90
+    }
91
+
92
+    /**
93
+     * Validates a username and password
94
+     *
95
+     * This method should return true or false depending on if login
96
+     * succeeded.
97
+     *
98
+     * @param string $username
99
+     * @param string $password
100
+     * @return bool
101
+     * @throws PasswordLoginForbidden
102
+     */
103
+    protected function validateUserPass($username, $password) {
104
+        if ($this->userSession->isLoggedIn() &&
105
+            $this->isDavAuthenticated($this->userSession->getUser()->getUID())
106
+        ) {
107
+            $this->session->close();
108
+            return true;
109
+        } else {
110
+            try {
111
+                if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) {
112
+                    $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID());
113
+                    $this->session->close();
114
+                    return true;
115
+                } else {
116
+                    $this->session->close();
117
+                    return false;
118
+                }
119
+            } catch (PasswordLoginForbiddenException $ex) {
120
+                $this->session->close();
121
+                throw new PasswordLoginForbidden();
122
+            }
123
+        }
124
+    }
125
+
126
+    /**
127
+     * @return array{bool, string}
128
+     * @throws NotAuthenticated
129
+     * @throws ServiceUnavailable
130
+     */
131
+    public function check(RequestInterface $request, ResponseInterface $response) {
132
+        try {
133
+            return $this->auth($request, $response);
134
+        } catch (NotAuthenticated $e) {
135
+            throw $e;
136
+        } catch (Exception $e) {
137
+            $class = get_class($e);
138
+            $msg = $e->getMessage();
139
+            \OC::$server->get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
140
+            throw new ServiceUnavailable("$class: $msg");
141
+        }
142
+    }
143
+
144
+    /**
145
+     * Checks whether a CSRF check is required on the request
146
+     */
147
+    private function requiresCSRFCheck(): bool {
148
+        // GET requires no check at all
149
+        if ($this->request->getMethod() === 'GET') {
150
+            return false;
151
+        }
152
+
153
+        // Official Nextcloud clients require no checks
154
+        if ($this->request->isUserAgent([
155
+            IRequest::USER_AGENT_CLIENT_DESKTOP,
156
+            IRequest::USER_AGENT_CLIENT_ANDROID,
157
+            IRequest::USER_AGENT_CLIENT_IOS,
158
+        ])) {
159
+            return false;
160
+        }
161
+
162
+        // If not logged-in no check is required
163
+        if (!$this->userSession->isLoggedIn()) {
164
+            return false;
165
+        }
166
+
167
+        // POST always requires a check
168
+        if ($this->request->getMethod() === 'POST') {
169
+            return true;
170
+        }
171
+
172
+        // If logged-in AND DAV authenticated no check is required
173
+        if ($this->userSession->isLoggedIn() &&
174
+            $this->isDavAuthenticated($this->userSession->getUser()->getUID())) {
175
+            return false;
176
+        }
177
+
178
+        return true;
179
+    }
180
+
181
+    /**
182
+     * @return array{bool, string}
183
+     * @throws NotAuthenticated
184
+     */
185
+    private function auth(RequestInterface $request, ResponseInterface $response): array {
186
+        $forcedLogout = false;
187
+
188
+        if (!$this->request->passesCSRFCheck() &&
189
+            $this->requiresCSRFCheck()) {
190
+            // In case of a fail with POST we need to recheck the credentials
191
+            if ($this->request->getMethod() === 'POST') {
192
+                $forcedLogout = true;
193
+            } else {
194
+                $response->setStatus(401);
195
+                throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
196
+            }
197
+        }
198
+
199
+        if ($forcedLogout) {
200
+            $this->userSession->logout();
201
+        } else {
202
+            if ($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
203
+                throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
204
+            }
205
+            if (
206
+                //Fix for broken webdav clients
207
+                ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) ||
208
+                //Well behaved clients that only send the cookie are allowed
209
+                ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ||
210
+                \OC_User::handleApacheAuth()
211
+            ) {
212
+                $user = $this->userSession->getUser()->getUID();
213
+                $this->currentUser = $user;
214
+                $this->session->close();
215
+                return [true, $this->principalPrefix . $user];
216
+            }
217
+        }
218
+
219
+        if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {
220
+            // do not re-authenticate over ajax, use dummy auth name to prevent browser popup
221
+            $response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"');
222
+            $response->setStatus(401);
223
+            throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
224
+        }
225
+
226
+        $data = parent::check($request, $response);
227
+        if ($data[0] === true) {
228
+            $startPos = strrpos($data[1], '/') + 1;
229
+            $user = $this->userSession->getUser()->getUID();
230
+            $data[1] = substr_replace($data[1], $user, $startPos);
231
+        }
232
+        return $data;
233
+    }
234 234
 }
Please login to merge, or discard this patch.