Skip to content

Commit

Permalink
Implement SSO Identity Profile Completion (#131)
Browse files Browse the repository at this point in the history
* implement sso identity profile completion, resolves #121
* update docs
* fix unavilable twig helper call
* improve oauth parameter bag
  • Loading branch information
solverat authored Feb 26, 2020
1 parent 26e1ce1 commit d27f572
Show file tree
Hide file tree
Showing 44 changed files with 978 additions and 134 deletions.
8 changes: 4 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ env:
- PIMCORE_PHP_ERROR_REPORTING=32767 # E_ALL
matrix:
include:
- name: "PHPStan Static Analysis (Pimcore 6.5.x, Symfony ^4.0, PHP 7.4)"
- name: "PHPStan Static Analysis (Pimcore 6.5.x, Symfony ^4.0, PHP 7.3)"
sudo: required
php: 7.4
php: 7.3
env:
- PIMCORE_SKELETON_BRANCH="tags/v2.5.0"
- DACHCOM_TEST_SECTION=phpstan
- name: "PHP ECS (Pimcore 6.5.x, Symfony ^4.0, PHP 7.4)"
- name: "PHP ECS (Pimcore 6.5.x, Symfony ^4.0, PHP 7.3)"
sudo: required
php: 7.4
php: 7.3
env:
- PIMCORE_SKELETON_BRANCH="tags/v2.5.0"
- DACHCOM_TEST_SECTION=ecs
Expand Down
50 changes: 49 additions & 1 deletion docs/40_Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,35 @@

***

### members.oauth.sso_instance.complete_profile.success

| Type | Reference |
|:--- |:--- |
| **const** | `\MembersEvent:OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_SUCCESS` |
| **name** | `members.oauth.sso_instance.complete_profile.success` |
| **class** | `\MembersBundle\Event\FormEvent` |
| **description** | The OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_SUCCESS event occurs when the complete-profile form is submitted successfully. This event allows you to set the response instead of using the default one. |

### members.oauth.sso_instance.complete_profile.completed

| Type | Reference |
|:--- |:--- |
| **const** | `\MembersEvent:OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_COMPLETED` |
| **name** | `members.oauth.sso_instance.complete_profile.completed` |
| **class** | `\MembersBundle\Event\FilterUserResponseEvent` |
| **description** | The OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_COMPLETED event occurs after saving the user in the complete-profile process. This event allows you to access the response which will be sent. |

### members.oauth.sso_instance.complete_profile.failure

| Type | Reference |
|:--- |:--- |
| **const** | `\MembersEvent:OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_FAILURE` |
| **name** | `members.oauth.sso_instance.complete_profile.failure` |
| **class** | `\MembersBundle\Event\FormEvent` |
| **description** | The OAUTH_SSO_INSTANCE_COMPLETE_PROFILE_FAILURE event occurs when the complete-profile form is not valid. This event allows you to set the response instead of using the default one. |

***

### members.oauth.connection.success

| Type | Reference |
Expand All @@ -248,7 +277,6 @@
| **class** | `\MembersBundle\Event\OAuth\OAuthResourceEvent` |
| **description** | The OAUTH_RESOURCE_MAPPING_PROFILE event occurs before a sso identity gets assigned to given user profile. This event allows you to map resource data (e.g. google) to your user identity. |


### members.oauth.resource_mapping.registration

| Type | Reference |
Expand All @@ -257,3 +285,23 @@
| **name** | `members.oauth.connection.success` |
| **class** | `\MembersBundle\Event\OAuth\OAuthResourceEvent` |
| **description** | The OAUTH_RESOURCE_MAPPING_REGISTRATION event occurs before the registration form gets rendered. This event allows you to map resource data (e.g. google) to your registration form. |

***

### members.oauth.identity_status.profile_completion

| Type | Reference |
|:--- |:--- |
| **const** | `\MembersEvent:OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION` |
| **name** | `members.oauth.identity_status.profile_completion` |
| **class** | `\MembersBundle\Event\OAuth\OAuthIdentityEvent` |
| **description** | The OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION event occurs before a user enters the profile completion step. This event allows you to overrule the termination if a user is able to complete is profile or not. |

### members.oauth.identity_status.deletion

| Type | Reference |
|:--- |:--- |
| **const** | `\MembersEvent:OAUTH_IDENTITY_STATUS_DELETION` |
| **name** | `members.oauth.identity_status.deletion` |
| **class** | `\MembersBundle\Event\OAuth\OAuthIdentityEvent` |
| **description** | The OAUTH_IDENTITY_STATUS_DELETION event occurs before a identity gets deleted. This event allows you to overrule the termination if a identity can be deleted (after sso identity has been deleted). |
2 changes: 2 additions & 0 deletions docs/SSO/10_Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Members supports integration with third-party services for single sign on.
Services like Google, Facebook etc. can be integrated and used as SSO providers for user registration and user login while Members will act as SSO client.

![image](https://user-images.githubusercontent.com/700119/75279841-a176f100-580c-11ea-84ee-ff8ca12f3c4a.png)

## Dependencies
We're using the [KnpUOAuth2ClientBundle](https://github.com/knpuniversity/oauth2-client-bundle) which relies on [league/oauth2-client](https://oauth2-client.thephpleague.com).

Expand Down
13 changes: 11 additions & 2 deletions docs/SSO/11_IntegrationTypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ There are two basic integration types:
## I. Login
Triggers, if a new user want's to connect. That said, there are two ways to complete users registration:

### Instant Login
### I.I Instant Login
A successfully connected users gets logged in instantly.

```yaml
Expand All @@ -14,7 +14,14 @@ members:
activation_type: 'instant'
```
### Login via Profile Completion
#### Delayed Profile Completion
![image](https://user-images.githubusercontent.com/700119/75279916-c2d7dd00-580c-11ea-9f19-5d326a1d69b0.png)
It's possible to complete this "unknown" users profile in later step which uses a dedicated form type (`Form/Type/Sso/CompleteProfileFormType.php`).
By default, this completion is possible, as long the instant created SSO identity has no given password.
If you want to change that, use the [status event](./32_IdentityStatusListener.md#OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION).

### I.II Login via Profile Completion
After the request was successful, the user gets redirected to the registration form.
There is no need to enter a password. Some fields may already be pre-filled (Read more about it [here](./12_ResourceMapping.md).

Expand All @@ -27,6 +34,8 @@ members:
activation_type: 'complete_profile'
```

***

## II. Connect
Triggers, if an existing user want's to connect a profile with a given provider (e.g. Google).
A list of all possible connectors are available in user's profile (There are some [twig extensions](./30_TwigExtensions.md)).
2 changes: 1 addition & 1 deletion docs/SSO/12_ResourceMapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ However, if you're adding your own EventListener, the mapping is totally up to y
There are two different types of mapping events:

### OAUTH_RESOURCE_MAPPING_PROFILE
The `OAUTH_RESOURCE_MAPPING_PROFILE` event occurs before a sso identity gets assigned to given user profile.
The `OAUTH_RESOURCE_MAPPING_PROFILE` event occurs before a SSO Identity gets assigned to given user profile.
This Event fires shortly before the SSO Identity gets applied to the user.

> **Attention**: This event also fires after an existing user gets connected to a client. You may don't want to override existing values!
Expand Down
20 changes: 17 additions & 3 deletions docs/SSO/30_TwigExtensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Twig Extension

> **Note:** Always call `members_system_oauth_enabled()` first, the oauth extension could be unavailable!
## members_system_oauth_enabled
Check if oauth has been enabled:

Expand All @@ -13,9 +15,21 @@ Check if oauth has been enabled:
List all available social connectors:

```twig
{% set social_route_name = 'members_user_security_oauth_login' %}
{% set skip_connected_identities = false %}
{% if members_system_oauth_enabled() == true %}
{% set social_route_name = 'members_user_security_oauth_login' %}
{% set skip_connected_identities = false %}
{% dump(members_oauth_social_links(social_route_name, skip_connected_identities)) %}
{% endif %}
```

{% dump(members_oauth_social_links(social_route_name, skip_connected_identities)) %}
## members_oauth_can_complete_profile
Check if a new registered SSO user can enter the profile completion route:

```twig
{% if members_system_oauth_enabled() == true %}
{% if members_oauth_can_complete_profile() == true %}
{# do something funky #}
{% endif %}
{% endif %}
```
4 changes: 3 additions & 1 deletion docs/SSO/31_Listener.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ There two ways to clean up expired identities.
> **Note:** This feature is not enabled by default!
### Important Notes
This Clean-Up task also removes the related user within some exceptions:
This Clean-Up task also removes the related user within some default exceptions:
- The user will **not be deleted** if the given user has other SSO identities
- The user will **not be deleted** if a password has been defined (not empty rule)

You're able to change the default exceptions by alter the `OAUTH_IDENTITY_STATUS_DELETION` event. Read more about it [here](./32_IdentityStatusListener.md).

### I. Expired Date
Use the `clean_up_expired_tokens` configuration flag to enable the clean-up.
In this example, all identities, older than `expiresAt` will be removed.
Expand Down
73 changes: 73 additions & 0 deletions docs/SSO/32_IdentityStatusListener.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# SSO Identity Listeners
If you need to hook into core decisions, you may want to use som identity state listener:

## OAUTH_IDENTITY_STATUS_DELETION
Use this event to change the deletion status of a SSO Identity.
This event triggers, if you have enabled the [identity clean-up task](./31_Listener.md).

```php
<?php

namespace AppBundle\EventListener;

use MembersBundle\MembersEvents;
use MembersBundle\Event\OAuth\OAuthIdentityEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class IdentityStatusDeletionEvent implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
MembersEvents::OAUTH_IDENTITY_STATUS_DELETION => 'onDispatch'
];
}

public function onDispatch(OAuthIdentityEvent $event)
{
$user = $event->getIdentity();

// this is just an example
if (!empty($user->getLastLogin())) {
// this will prevent the user deletion
$event->setIdentityDispatchStatus(false);
}
}
}
```

## OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION
Use this event to change the status definition if a sso only user is able to call the completion profile route.
By default, an instantly logged in user can complete his profile afterwards in his profile (`/en/members/profile/`, **if no password** has been set.
If you want to change that, use the `OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION` event:

```php
<?php

namespace AppBundle\EventListener;

use MembersBundle\MembersEvents;
use MembersBundle\Event\OAuth\OAuthIdentityEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class IdentityStatusProfileCompletionEvent implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
MembersEvents::OAUTH_IDENTITY_STATUS_PROFILE_COMPLETION => 'onDispatch'
];
}

public function onDispatch(OAuthIdentityEvent $event)
{
$user = $event->getIdentity();

// this is just an example
if (!empty($user->getUsername())) {
// this will prevent the user deletion
$event->setIdentityDispatchStatus(false);
}
}
}
```
5 changes: 4 additions & 1 deletion src/MembersBundle/Adapter/User/UserInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace MembersBundle\Adapter\User;

interface UserInterface extends \Symfony\Component\Security\Core\User\UserInterface
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface as BaseUserInterface;

interface UserInterface extends BaseUserInterface, EquatableInterface
{
const ROLE_DEFAULT = 'ROLE_USER';

Expand Down
9 changes: 9 additions & 0 deletions src/MembersBundle/Adapter/User/UserTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use MembersBundle\Adapter\Group\GroupInterface;
use Pimcore\Model\DataObject\ClassDefinition\Data\Password;
use Symfony\Component\Security\Core\User\UserInterface;

trait UserTrait
{
Expand Down Expand Up @@ -60,6 +61,14 @@ public function eraseCredentials()
$field->getDataForResource($this->getPassword(), $this);
}

/**
* {@inheritdoc}
*/
public function isEqualTo(UserInterface $user)
{
return $user instanceof self && $user->getId() === $this->getId();
}

/**
* @return array
*/
Expand Down
Loading

0 comments on commit d27f572

Please sign in to comment.