Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OAuth2 authentication, source property on Events #163

Merged
merged 12 commits into from
Aug 22, 2020
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 29 additions & 4 deletions config/google-calendar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 23 additions & 0 deletions src/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ public function __get($name)
return $this->getSortDate();
}

if ($name === 'source') {
return [
'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) {
Expand All @@ -144,6 +151,12 @@ public function __set($name, $value)
return;
}

if ($name == 'source') {
$this->setSourceProperty($value);

return;
}

Arr::set($this->googleEvent, $name, $value);
}

Expand Down Expand Up @@ -246,6 +259,16 @@ protected function setDateProperty(string $name, CarbonInterface $date)
}
}

protected function setSourceProperty(array $value)
{
$source = new \Google_Service_Calendar_EventSource([
akmolina28 marked this conversation as resolved.
Show resolved Hide resolved
'title' => $value['title'],
'url' => $value['url'],
]);

$this->googleEvent->setSource($source);
}

protected function getFieldName(string $name): string
{
return [
Expand Down
31 changes: 30 additions & 1 deletion src/GoogleCalendarFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,43 @@ public static function createForCalendarId(string $calendarId): GoogleCalendar
}

public static function createAuthenticatedGoogleClient(array $config): Google_Client
{
$authProfile = $config['default_auth_profile'];

switch ($authProfile) {
akmolina28 marked this conversation as resolved.
Show resolved Hide resolved
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}].");
akmolina28 marked this conversation as resolved.
Show resolved Hide resolved
}

protected static function createServiceAccountClient(array $authProfile): Google_Client
{
$client = new Google_Client;

$client->setScopes([
Google_Service_Calendar::CALENDAR,
]);

$client->setAuthConfig($config['service_account_credentials_json']);
$client->setAuthConfig($authProfile['credentials_json']);
akmolina28 marked this conversation as resolved.
Show resolved Hide resolved

return $client;
}

protected static function createOAuthClient(array $authProfile): Google_Client
{
$client = new Google_Client;

$client->setScopes([
Google_Service_Calendar::CALENDAR,
]);

$client->setAuthConfig($authProfile['credentials_json']);

$client->setAccessToken(file_get_contents($authProfile['token_json']));

return $client;
}
Expand Down
42 changes: 37 additions & 5 deletions src/GoogleCalendarServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
akmolina28 marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
12 changes: 12 additions & 0 deletions tests/Integration/EventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
'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()
{
Expand Down