diff --git a/config-dist.php b/config-dist.php index 79475979..41de5d83 100644 --- a/config-dist.php +++ b/config-dist.php @@ -96,6 +96,15 @@ Setting('LDAP_BIND_PW', ''); // Password for the above account Setting('LDAP_USER_FILTER', ''); // Example value "(OU=grocy_users)" Setting('LDAP_UID_ATTR', ''); // Windows AD: "sAMAccountName", OpenLDAP: "uid", GLAuth: "cn" +// Options when using OAuthMiddleware +Setting('OAUTH_CLIENT_ID', ''); +Setting('OAUTH_CLIENT_SECRET', ''); +Setting('OAUTH_SCOPES', 'openid profile'); +Setting('OAUTH_USERNAME_CLAIM', 'preferred_username'); +Setting('OAUTH_AUTH_URL', ''); +Setting('OAUTH_TOKEN_URL', ''); +Setting('OAUTH_USERINFO_URL', ''); + // Default permissions for new users // the array needs to contain the technical/constant names // See the file controllers/Users/User.php for possible values diff --git a/middleware/OAuthMiddleware.php b/middleware/OAuthMiddleware.php new file mode 100644 index 00000000..3d94e414 --- /dev/null +++ b/middleware/OAuthMiddleware.php @@ -0,0 +1,109 @@ +client = new Client(['timeout' => 2.0]); + } + + public function authenticate(Request $request) + { + define('GROCY_EXTERNALLY_MANAGED_AUTHENTICATION', true); + + // First try to authenticate by API key + $auth = new ApiKeyAuthMiddleware($this->AppContainer, $this->ResponseFactory); + $user = $auth->authenticate($request); + if ($user !== null) + { + return $user; + } + + // Then by session cookie + $auth = new SessionAuthMiddleware($this->AppContainer, $this->ResponseFactory); + $user = $auth->authenticate($request); + if ($user !== null) + { + return $user; + } + + // no active session -> start OAuth flow + // 1. redirect to auth URL (only if code parameter not already set) + $code = $request->getQueryParam('code'); + if ($code === null) { + $response = $this->ResponseFactory->createResponse(); + if (string_starts_with($request->getUri()->getPath(), '/api/')) { + // no OAuth for API calls + return $response->withStatus(401); + } else { + return $response->withRedirect( + GROCY_OAUTH_AUTH_URL . + "?response_type=code" . + "&client_id=" . GROCY_OAUTH_CLIENT_ID . + "&redirect_uri=" . $request->getUri() . + "&scope=" . GROCY_OAUTH_SCOPES + ); + } + } + + // 2. handle callback from auth server + // -> code parameter given to get token from aut server + $tokenResponse = $this->client->request('POST', GROCY_OAUTH_TOKEN_URL, [ + RequestOptions::AUTH => [GROCY_OAUTH_CLIENT_ID, GROCY_OAUTH_CLIENT_SECRET], + RequestOptions::FORM_PARAMS => [ + "grant_type" => "authorization_code", + "code" => $code, + "redirect_uri" => (string)$request->getUri(), + ], + ]); + if ($tokenResponse->getStatusCode() != 200) { + throw new \Exception('token request failed. Status code: ' . $response->getStatusCode()); + } + $tokenResponseJson = json_decode($tokenResponse->getBody(), true); + + // auth successful -> start collection user information + $infoResponse = $this->client->request('POST', GROCY_OAUTH_USERINFO_URL, [ + RequestOptions::HEADERS => [ + "Authorization" => "Bearer " . $tokenResponseJson["access_token"] + ], + ]); + if ($infoResponse->getStatusCode() != 200) { + throw new \Exception('user info request failed error: ' . $response->getStatusCode()); + } + $infoResponseJson = json_decode($infoResponse->getBody(), true); + + // get user from database or create one if needed + $db = DatabaseService::getInstance()->GetDbConnection(); + $user = $db->users()->where('username', $infoResponseJson[GROCY_OAUTH_USERNAME_CLAIM])->fetch(); + if ($user == null) { + $user = UsersService::getInstance()->CreateUser($infoResponseJson[GROCY_OAUTH_USERNAME_CLAIM], '', '', ''); + } + + self::SetSessionCookie(SessionService::getInstance()->CreateSession($user->id, false)); + + return $user; + } + + public static function ProcessLogin(array $postParams) + { + throw new \Exception('Not implemented'); + } +}