From 0d7894a4184fbe7ac644ce7ffed39f29baac77e0 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Wed, 5 Aug 2020 21:14:14 +0000 Subject: [PATCH 01/10] Added support for OAuth authentication, allowing for calendar api access using a real google account rather than a service account --- config/google-calendar.php | 33 ++++++++++++++++++--- src/GoogleCalendarFactory.php | 32 +++++++++++++++++++- src/GoogleCalendarServiceProvider.php | 42 +++++++++++++++++++++++---- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/config/google-calendar.php b/config/google-calendar.php index 9fb1712..1cf7fd0 100644 --- a/config/google-calendar.php +++ b/config/google-calendar.php @@ -2,10 +2,35 @@ return [ - /* - * Path to the json file containing the credentials. - */ - 'service_account_credentials_json' => storage_path('app/google-calendar/service-account-credentials.json'), + 'default_auth_profile' => env('GOOGLE_CALENDAR_AUTH_PROFILE', 'service_account'), + + 'auth_profiles' => [ + + /* + * Authenticate using a service account. + */ + 'service_account' => [ + /* + * Path to the json file containing the credentials. + */ + 'credentials_json' => storage_path('app/google-calendar/service-account-credentials.json') + ], + + /* + * Authenticate with actual google user account. + */ + 'oauth' => [ + /* + * Path to the json file containing the oauth2 credentials. + */ + 'credentials_json' => storage_path('app/google-calendar/oauth-credentials.json'), + + /* + * Path to the json file containing the oauth2 token. + */ + 'token_json' => storage_path('app/google-calendar/oauth-token.json') + ] + ], /* * The id of the Google Calendar that will be used by default. diff --git a/src/GoogleCalendarFactory.php b/src/GoogleCalendarFactory.php index 63c04d4..e343590 100644 --- a/src/GoogleCalendarFactory.php +++ b/src/GoogleCalendarFactory.php @@ -19,6 +19,33 @@ public static function createForCalendarId(string $calendarId): GoogleCalendar } public static function createAuthenticatedGoogleClient(array $config): Google_Client + { + $authProfile = $config['default_auth_profile']; + + switch($authProfile) { + case 'service_account': + return self::createServiceAccountClient($config['auth_profiles']['service_account']); + case 'oauth': + return self::createOAuthClient($config['auth_profiles']['oauth']); + } + + throw new \InvalidArgumentException("Unsupported authentication profile [{$authProfile}]."); + } + + protected static function createServiceAccountClient(array $authProfile): Google_Client + { + $client = new Google_Client; + + $client->setScopes([ + Google_Service_Calendar::CALENDAR, + ]); + + $client->setAuthConfig($authProfile['credentials_json']); + + return $client; + } + + protected static function createOAuthClient(array $authProfile): Google_Client { $client = new Google_Client; @@ -26,11 +53,14 @@ public static function createAuthenticatedGoogleClient(array $config): Google_Cl Google_Service_Calendar::CALENDAR, ]); - $client->setAuthConfig($config['service_account_credentials_json']); + $client->setAuthConfig($authProfile['credentials_json']); + + $client->setAccessToken(file_get_contents($authProfile['token_json'])); return $client; } + protected static function createCalendarClient(Google_Service_Calendar $service, string $calendarId): GoogleCalendar { return new GoogleCalendar($service, $calendarId); diff --git a/src/GoogleCalendarServiceProvider.php b/src/GoogleCalendarServiceProvider.php index c706215..a9350f3 100644 --- a/src/GoogleCalendarServiceProvider.php +++ b/src/GoogleCalendarServiceProvider.php @@ -35,14 +35,46 @@ protected function guardAgainstInvalidConfiguration(array $config = null) throw InvalidConfiguration::calendarIdNotSpecified(); } - $credentials = $config['service_account_credentials_json']; + $authProfile = $config['default_auth_profile']; - if (! is_array($credentials) && ! is_string($credentials)) { - throw InvalidConfiguration::credentialsTypeWrong($credentials); + switch ($authProfile) { + case 'service_account': + $this->validateServiceAccountConfigSettings($config); + break; + case 'oauth': + $this->validateOAuthConfigSettings($config); + break; + default: + throw new \InvalidArgumentException("Unsupported authentication profile [{$authProfile}]."); + } + } + + protected function validateServiceAccountConfigSettings(array $config = null) + { + $credentials = $config['auth_profiles']['service_account']['credentials_json']; + + $this->validateConfigSetting($credentials); + } + + protected function validateOAuthConfigSettings(array $config = null) + { + $credentials = $config['auth_profiles']['oauth']['credentials_json']; + + $this->validateConfigSetting($credentials); + + $token = $config['auth_profiles']['oauth']['token_json']; + + $this->validateConfigSetting($token); + } + + protected function validateConfigSetting(string $setting) + { + if (! is_array($setting) && ! is_string($setting)) { + throw InvalidConfiguration::credentialsTypeWrong($setting); } - if (is_string($credentials) && ! file_exists($credentials)) { - throw InvalidConfiguration::credentialsJsonDoesNotExist($credentials); + if (is_string($setting) && ! file_exists($setting)) { + throw InvalidConfiguration::credentialsJsonDoesNotExist($setting); } } } From 9eb62bd9360a5dbdcbd242ccb670cdc2c4f2d0f8 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Wed, 5 Aug 2020 22:28:13 +0000 Subject: [PATCH 02/10] Added support for the event source property --- src/Event.php | 23 +++++++++++++++++++++++ tests/Integration/EventTest.php | 12 ++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Event.php b/src/Event.php index 424ae02..0132470 100644 --- a/src/Event.php +++ b/src/Event.php @@ -121,6 +121,13 @@ public function __get($name) return $this->getSortDate(); } + if ($name === 'source') { + return array([ + 'title' => $this->googleEvent->getSource()->title, + 'url' => $this->googleEvent->getSource()->url + ]); + } + $value = Arr::get($this->googleEvent, $name); if (in_array($name, ['start.date', 'end.date']) && $value) { @@ -144,6 +151,12 @@ public function __set($name, $value) return; } + if ($name == 'source') { + $this->setSourceProperty($value); + + return; + } + Arr::set($this->googleEvent, $name, $value); } @@ -246,6 +259,16 @@ protected function setDateProperty(string $name, CarbonInterface $date) } } + protected function setSourceProperty(array $value) + { + $source = new \Google_Service_Calendar_EventSource([ + 'title' => $value['title'], + 'url' => $value['url'] + ]); + + $this->googleEvent->setSource($source); + } + protected function getFieldName(string $name): string { return [ diff --git a/tests/Integration/EventTest.php b/tests/Integration/EventTest.php index a16e6ad..e865c25 100644 --- a/tests/Integration/EventTest.php +++ b/tests/Integration/EventTest.php @@ -100,6 +100,18 @@ public function it_can_set_a_location() $this->assertEquals('Test Location', $this->event->googleEvent->getLocation()); } + /** @test */ + public function it_can_set_a_source() + { + $this->event->source = array( + 'title' => 'Test Source Title', + 'url' => 'http://testsource.url' + ); + + $this->assertEquals('Test Source Title', $this->event->googleEvent->getSource()->title); + $this->assertEquals('http://testsource.url', $this->event->googleEvent->getSource()->url); + } + /** @test */ public function it_can_determine_if_an_event_is_an_all_day_event() { From 592cf11cea848f2f97cc35de99d2139470fc207d Mon Sep 17 00:00:00 2001 From: Andrew <2997206+akmolina28@users.noreply.github.com> Date: Wed, 5 Aug 2020 18:35:01 -0500 Subject: [PATCH 03/10] Update README.md --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index c50c194..09f342a 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,26 @@ Scroll down to the "Integrate calendar" section to see the id of the calendar. Y ![10](./docs/v2/10.png) +### Authentication with OAuth2 + +This package now supports OAuth2 authentication, allowing you to authenticate with an actual Google account. This makes it possible to create and manage events with your own Google account rather than a separate service account. This also enables you to use source urls in your events, which are only visible to the creator of the event (see [docs](https://developers.google.com/calendar/v3/reference/events) for more on the source property). + +OAuth2 authentication requires a token file, in addition to the credentials file. The easiest way to generate both of these files is by using the [php quickstart tool](https://developers.google.com/calendar/quickstart/php). Following this guide will generate two files, `credentials.json` and `token.json`. They must be saved to your project as `oauth-credentials.json` and `oauth-token.json`, respectively. Check the config file in this package for exact details on where to save these files. + +To use OAuth2, you must also set a new environment variable in your .env file: + +```php +GOOGLE_CALENDAR_AUTH_PROFILE=oauth +``` + +If you are upgrading from an older version of this package, you will need to force a publish of the configuration: + +```bash +php artisan vendor:publish --provider="Spatie\GoogleCalendar\GoogleCalendarServiceProvider" --force +``` + +Finally, for a more seamless experience in your application, instead of using the quickstart tool you can set up a consent screen in the [Google API console](https://console.developers.google.com/apis). This would allow non-technical users of your application to easily generate their own tokens. This is completely optional. + ## Usage ### Getting events From 22729e6172ef773d78445b0ee839b062d5c4a2e7 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Wed, 5 Aug 2020 20:32:08 -0500 Subject: [PATCH 04/10] Fixed styles --- config/google-calendar.php | 4 ++-- src/Event.php | 8 ++++---- src/GoogleCalendarFactory.php | 2 +- tests/Integration/EventTest.php | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/google-calendar.php b/config/google-calendar.php index 1cf7fd0..05e9b12 100644 --- a/config/google-calendar.php +++ b/config/google-calendar.php @@ -13,7 +13,7 @@ /* * Path to the json file containing the credentials. */ - 'credentials_json' => storage_path('app/google-calendar/service-account-credentials.json') + 'credentials_json' => storage_path('app/google-calendar/service-account-credentials.json'), ], /* @@ -28,7 +28,7 @@ /* * Path to the json file containing the oauth2 token. */ - 'token_json' => storage_path('app/google-calendar/oauth-token.json') + 'token_json' => storage_path('app/google-calendar/oauth-token.json'), ] ], diff --git a/src/Event.php b/src/Event.php index 0132470..be1cccd 100644 --- a/src/Event.php +++ b/src/Event.php @@ -122,10 +122,10 @@ public function __get($name) } if ($name === 'source') { - return array([ + return [ 'title' => $this->googleEvent->getSource()->title, - 'url' => $this->googleEvent->getSource()->url - ]); + 'url' => $this->googleEvent->getSource()->url, + ]; } $value = Arr::get($this->googleEvent, $name); @@ -263,7 +263,7 @@ protected function setSourceProperty(array $value) { $source = new \Google_Service_Calendar_EventSource([ 'title' => $value['title'], - 'url' => $value['url'] + 'url' => $value['url'], ]); $this->googleEvent->setSource($source); diff --git a/src/GoogleCalendarFactory.php b/src/GoogleCalendarFactory.php index e343590..d4af006 100644 --- a/src/GoogleCalendarFactory.php +++ b/src/GoogleCalendarFactory.php @@ -22,7 +22,7 @@ public static function createAuthenticatedGoogleClient(array $config): Google_Cl { $authProfile = $config['default_auth_profile']; - switch($authProfile) { + switch ($authProfile) { case 'service_account': return self::createServiceAccountClient($config['auth_profiles']['service_account']); case 'oauth': diff --git a/tests/Integration/EventTest.php b/tests/Integration/EventTest.php index e865c25..b6138b8 100644 --- a/tests/Integration/EventTest.php +++ b/tests/Integration/EventTest.php @@ -103,10 +103,10 @@ public function it_can_set_a_location() /** @test */ public function it_can_set_a_source() { - $this->event->source = array( + $this->event->source = [ 'title' => 'Test Source Title', - 'url' => 'http://testsource.url' - ); + 'url' => 'http://testsource.url', + ]; $this->assertEquals('Test Source Title', $this->event->googleEvent->getSource()->title); $this->assertEquals('http://testsource.url', $this->event->googleEvent->getSource()->url); From c9b315d15075279c9e17fd9b6d4d6ded611e85b7 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Thu, 6 Aug 2020 01:37:17 +0000 Subject: [PATCH 05/10] Fixed styles --- config/google-calendar.php | 2 +- src/GoogleCalendarFactory.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/config/google-calendar.php b/config/google-calendar.php index 05e9b12..d6b3ea4 100644 --- a/config/google-calendar.php +++ b/config/google-calendar.php @@ -29,7 +29,7 @@ * Path to the json file containing the oauth2 token. */ 'token_json' => storage_path('app/google-calendar/oauth-token.json'), - ] + ], ], /* diff --git a/src/GoogleCalendarFactory.php b/src/GoogleCalendarFactory.php index d4af006..a6684d5 100644 --- a/src/GoogleCalendarFactory.php +++ b/src/GoogleCalendarFactory.php @@ -60,7 +60,6 @@ protected static function createOAuthClient(array $authProfile): Google_Client return $client; } - protected static function createCalendarClient(Google_Service_Calendar $service, string $calendarId): GoogleCalendar { return new GoogleCalendar($service, $calendarId); From c63a1dfd3526b685ff593a67f38b5120011fe24e Mon Sep 17 00:00:00 2001 From: Freek Van der Herten Date: Thu, 6 Aug 2020 09:18:33 +0200 Subject: [PATCH 06/10] Update README.md --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09f342a..483b778 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Scroll down to the "Integrate calendar" section to see the id of the calendar. Y ### Authentication with OAuth2 -This package now supports OAuth2 authentication, allowing you to authenticate with an actual Google account. This makes it possible to create and manage events with your own Google account rather than a separate service account. This also enables you to use source urls in your events, which are only visible to the creator of the event (see [docs](https://developers.google.com/calendar/v3/reference/events) for more on the source property). +This package supports OAuth2 authentication. This allows you to authenticate with an actual Google account, and to create and manage events with your own Google account. OAuth2 authentication requires a token file, in addition to the credentials file. The easiest way to generate both of these files is by using the [php quickstart tool](https://developers.google.com/calendar/quickstart/php). Following this guide will generate two files, `credentials.json` and `token.json`. They must be saved to your project as `oauth-credentials.json` and `oauth-token.json`, respectively. Check the config file in this package for exact details on where to save these files. @@ -264,6 +264,17 @@ $event = Event::find($eventId); $event->delete(); ``` +## Setting a source + +You can set source urls in your events, which are only visible to the creator of the event (see [docs](https://developers.google.com/calendar/v3/reference/events) for more on the source property). This function only works when authenticated via OAuth. + +```php +$yourEvent->source = [ + 'title' => 'Test Source Title', + 'url' => 'http://testsource.url', + ]; + ``` + ### Limitations The Google Calendar API provides many options. This package doesn't support all of them. For instance, recurring events cannot be managed properly with this package. If you stick to creating events with a name and a date you should be fine. From 1a7d4087edf762e7c37afada6074751819c287f8 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Thu, 6 Aug 2020 08:15:32 -0500 Subject: [PATCH 07/10] Code cleanup -- remove switch statements, use custom exceptions, import namespaces. --- src/Event.php | 3 ++- src/Exceptions/InvalidConfiguration.php | 5 +++++ src/GoogleCalendarFactory.php | 13 +++++++------ src/GoogleCalendarServiceProvider.php | 19 ++++++++++--------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Event.php b/src/Event.php index be1cccd..d71eaf7 100644 --- a/src/Event.php +++ b/src/Event.php @@ -7,6 +7,7 @@ use DateTime; use Google_Service_Calendar_Event; use Google_Service_Calendar_EventDateTime; +use Google_Service_Calendar_EventSource; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; @@ -261,7 +262,7 @@ protected function setDateProperty(string $name, CarbonInterface $date) protected function setSourceProperty(array $value) { - $source = new \Google_Service_Calendar_EventSource([ + $source = new Google_Service_Calendar_EventSource([ 'title' => $value['title'], 'url' => $value['url'], ]); diff --git a/src/Exceptions/InvalidConfiguration.php b/src/Exceptions/InvalidConfiguration.php index 6d00818..c7715d6 100644 --- a/src/Exceptions/InvalidConfiguration.php +++ b/src/Exceptions/InvalidConfiguration.php @@ -20,4 +20,9 @@ public static function credentialsTypeWrong($credentials) { return new static(sprintf('Credentials should be an array or the path of json file. "%s was given.', gettype($credentials))); } + + public static function invalidAuthenticationProfile($authProfile) + { + return new static("Authentication profile [{$authProfile}] does not match any of the supported authentication types."); + } } diff --git a/src/GoogleCalendarFactory.php b/src/GoogleCalendarFactory.php index a6684d5..3687d65 100644 --- a/src/GoogleCalendarFactory.php +++ b/src/GoogleCalendarFactory.php @@ -4,6 +4,7 @@ use Google_Client; use Google_Service_Calendar; +use Spatie\GoogleCalendar\Exceptions\InvalidConfiguration; class GoogleCalendarFactory { @@ -22,14 +23,14 @@ public static function createAuthenticatedGoogleClient(array $config): Google_Cl { $authProfile = $config['default_auth_profile']; - switch ($authProfile) { - case 'service_account': - return self::createServiceAccountClient($config['auth_profiles']['service_account']); - case 'oauth': - return self::createOAuthClient($config['auth_profiles']['oauth']); + if ($authProfile === 'service_account') { + return self::createServiceAccountClient($config['auth_profiles']['service_account']); + } + if ($authProfile === 'oauth') { + return self::createOAuthClient($config['auth_profiles']['oauth']); } - throw new \InvalidArgumentException("Unsupported authentication profile [{$authProfile}]."); + throw InvalidConfiguration::invalidAuthenticationProfile($authProfile); } protected static function createServiceAccountClient(array $authProfile): Google_Client diff --git a/src/GoogleCalendarServiceProvider.php b/src/GoogleCalendarServiceProvider.php index a9350f3..7fe2c8e 100644 --- a/src/GoogleCalendarServiceProvider.php +++ b/src/GoogleCalendarServiceProvider.php @@ -37,16 +37,17 @@ protected function guardAgainstInvalidConfiguration(array $config = null) $authProfile = $config['default_auth_profile']; - switch ($authProfile) { - case 'service_account': - $this->validateServiceAccountConfigSettings($config); - break; - case 'oauth': - $this->validateOAuthConfigSettings($config); - break; - default: - throw new \InvalidArgumentException("Unsupported authentication profile [{$authProfile}]."); + if ($authProfile === 'service_account') { + $this->validateServiceAccountConfigSettings($config); + return; } + + if ($authProfile === 'oauth') { + $this->validateOAuthConfigSettings($config); + return; + } + + throw InvalidConfiguration::invalidAuthenticationProfile($authProfile); } protected function validateServiceAccountConfigSettings(array $config = null) From 57382dcdd40ee78f8b6739bdebc852e7024c5f72 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Thu, 6 Aug 2020 13:57:33 +0000 Subject: [PATCH 08/10] Fixed styles --- src/GoogleCalendarServiceProvider.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/GoogleCalendarServiceProvider.php b/src/GoogleCalendarServiceProvider.php index 7fe2c8e..bca0c4a 100644 --- a/src/GoogleCalendarServiceProvider.php +++ b/src/GoogleCalendarServiceProvider.php @@ -39,11 +39,13 @@ protected function guardAgainstInvalidConfiguration(array $config = null) if ($authProfile === 'service_account') { $this->validateServiceAccountConfigSettings($config); + return; } if ($authProfile === 'oauth') { $this->validateOAuthConfigSettings($config); + return; } From 1268311a805a457597fc1617ce4559330a797e3d Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Thu, 6 Aug 2020 14:01:27 +0000 Subject: [PATCH 09/10] Fixed styles --- src/GoogleCalendarServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GoogleCalendarServiceProvider.php b/src/GoogleCalendarServiceProvider.php index bca0c4a..639521a 100644 --- a/src/GoogleCalendarServiceProvider.php +++ b/src/GoogleCalendarServiceProvider.php @@ -39,7 +39,7 @@ protected function guardAgainstInvalidConfiguration(array $config = null) if ($authProfile === 'service_account') { $this->validateServiceAccountConfigSettings($config); - + return; } From 0b0205464db1ac568189681aa0ee12fca19f9fd5 Mon Sep 17 00:00:00 2001 From: Andrew Molina <2997206+akmolina28@users.noreply.github.com> Date: Thu, 6 Aug 2020 14:01:27 +0000 Subject: [PATCH 10/10] Fixed styles --- src/GoogleCalendarServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GoogleCalendarServiceProvider.php b/src/GoogleCalendarServiceProvider.php index bca0c4a..639521a 100644 --- a/src/GoogleCalendarServiceProvider.php +++ b/src/GoogleCalendarServiceProvider.php @@ -39,7 +39,7 @@ protected function guardAgainstInvalidConfiguration(array $config = null) if ($authProfile === 'service_account') { $this->validateServiceAccountConfigSettings($config); - + return; }