diff --git a/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php b/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php index 3d488afd..a4bc4862 100644 --- a/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php +++ b/app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php @@ -493,14 +493,14 @@ final class OAuth2MembersApiController extends OAuth2ProtectedController * @param $member_id * @return mixed */ - public function resignFoundationMembership(){ + public function signCommunityMembership(){ try{ $member = $this->resource_server_context->getCurrentUser(); if(is_null($member)) return $this->error404(); - $member = $this->member_service->resignFoundationMembership($member); + $member = $this->member_service->signCommunityMembership($member); return $this->updated(SerializerRegistry::getInstance()->getSerializer ( @@ -523,4 +523,34 @@ final class OAuth2MembersApiController extends OAuth2ProtectedController } } + /** + * @param $member_id + * @return mixed + */ + public function resignMembership(){ + try{ + + $member = $this->resource_server_context->getCurrentUser(); + + if(is_null($member)) return $this->error404(); + + $this->member_service->resignMembership($member); + + return $this->deleted(); + } + catch (ValidationException $ex1) { + Log::warning($ex1); + return $this->error412(array($ex1->getMessage())); + } + catch(EntityNotFoundException $ex2) + { + Log::warning($ex2); + return $this->error404(array('message'=> $ex2->getMessage())); + } + catch (Exception $ex) { + Log::error($ex); + return $this->error500($ex); + } + } + } \ No newline at end of file diff --git a/app/Http/Middleware/OAuth2BearerAccessTokenRequestValidator.php b/app/Http/Middleware/OAuth2BearerAccessTokenRequestValidator.php index 5a4e6053..22d29d75 100644 --- a/app/Http/Middleware/OAuth2BearerAccessTokenRequestValidator.php +++ b/app/Http/Middleware/OAuth2BearerAccessTokenRequestValidator.php @@ -1,5 +1,4 @@ getUserExternalId(); $context['user_identifier'] = $token_info->getUserIdentifier(); $context['user_email'] = $token_info->getUserEmail(); + $context['user_email_verified'] = $token_info->isUserEmailVerified(); $context['user_first_name'] = $token_info->getUserFirstName(); $context['user_last_name'] = $token_info->getUserLastName(); $context['user_groups'] = $token_info->getUserGroups(); diff --git a/app/Http/routes.php b/app/Http/routes.php index 62a7023b..e8488276 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -43,7 +43,8 @@ Route::group([ Route::group(['prefix' => 'membership'], function(){ Route::put('foundation', ['uses' => 'OAuth2MembersApiController@signFoundationMembership']); - Route::put('community', ['uses' => 'OAuth2MembersApiController@resignFoundationMembership']); + Route::put('community', ['uses' => 'OAuth2MembersApiController@signCommunityMembership']); + Route::delete('resign', ['uses' => 'OAuth2MembersApiController@resignMembership']); }); }); diff --git a/app/ModelSerializers/AbstractMemberSerializer.php b/app/ModelSerializers/AbstractMemberSerializer.php index dd1408c3..34290485 100644 --- a/app/ModelSerializers/AbstractMemberSerializer.php +++ b/app/ModelSerializers/AbstractMemberSerializer.php @@ -36,6 +36,7 @@ class AbstractMemberSerializer extends SilverStripeSerializer 'Active' => 'active:json_boolean', 'EmailVerified' => 'email_verified:json_boolean', 'ProfilePhotoUrl' => 'pic:json_url', + 'MembershipType' => 'membership_type:json_string', ]; protected static $allowed_relations = [ diff --git a/app/Models/Foundation/Main/Member.php b/app/Models/Foundation/Main/Member.php index 494938a3..0ca6424d 100644 --- a/app/Models/Foundation/Main/Member.php +++ b/app/Models/Foundation/Main/Member.php @@ -1841,6 +1841,15 @@ SQL; $this->resign_date = new \DateTime('now', new \DateTimeZone(self::DefaultTimeZone)); } + public function resignMembership(){ + // Remove Member's Legal Agreements + $this->legal_agreements->clear(); + $this->affiliations->clear(); + $this->groups->clear(); + $this->membership_type = self::MembershipTypeNone; + $this->resign_date = new \DateTime('now', new \DateTimeZone(self::DefaultTimeZone)); + } + public function signFoundationMembership(LegalDocument $document) { if (!$this->isFoundationMember()) { diff --git a/app/Models/OAuth2/AccessToken.php b/app/Models/OAuth2/AccessToken.php index 7014d363..077f2e64 100644 --- a/app/Models/OAuth2/AccessToken.php +++ b/app/Models/OAuth2/AccessToken.php @@ -64,6 +64,11 @@ final class AccessToken extends Token */ private $user_email; + /** + * @var bool + */ + private $user_email_verified; + /** * @var string|null */ @@ -95,6 +100,7 @@ final class AccessToken extends Token $instance->user_external_id = self::getValueFromInfo('user_external_id', $token_info); $instance->user_identifier = self::getValueFromInfo('user_identifier', $token_info); $instance->user_email = self::getValueFromInfo('user_email', $token_info); + $instance->user_email_verified = boolval(self::getValueFromInfo('user_email_verified', $token_info)); $instance->user_first_name = self::getValueFromInfo('user_first_name', $token_info); $instance->user_last_name = self::getValueFromInfo('user_last_name', $token_info); $instance->auth_code = null; @@ -198,4 +204,13 @@ final class AccessToken extends Token public function getUserGroups():array { return $this->user_groups; } + + /** + * @return bool + */ + public function isUserEmailVerified(): bool + { + return $this->user_email_verified; + } + } \ No newline at end of file diff --git a/app/Models/OAuth2/ResourceServerContext.php b/app/Models/OAuth2/ResourceServerContext.php index 431652dc..8353c763 100644 --- a/app/Models/OAuth2/ResourceServerContext.php +++ b/app/Models/OAuth2/ResourceServerContext.php @@ -12,14 +12,14 @@ * limitations under the License. **/ -use App\Models\Foundation\Main\IGroup; +use App\Services\Model\dto\ExternalUserDTO; use App\Services\Model\IMemberService; use Illuminate\Support\Facades\Log; use libs\utils\ITransactionService; -use models\main\Group; use models\main\IGroupRepository; use models\main\IMemberRepository; use models\main\Member; + /** * Class ResourceServerContext * @package models\oauth2 @@ -186,6 +186,7 @@ final class ResourceServerContext implements IResourceServerContext $user_first_name = $this->getAuthContextVar('user_first_name'); $user_last_name = $this->getAuthContextVar('user_last_name'); $user_email = $this->getAuthContextVar('user_email'); + $user_email_verified = boolval($this->getAuthContextVar('user_email_verified')); if(!empty($user_email)) $member->setEmail($user_email); @@ -194,16 +195,18 @@ final class ResourceServerContext implements IResourceServerContext if(!empty($user_last_name)) $member->setLastName($user_last_name); + $member->setEmailVerified($user_email_verified); return $synch_groups ? $this->checkGroups($member) : $member; } } if(is_null($member)) { // we assume that is new idp version and claims already exists on context - $user_external_id = $this->getAuthContextVar('user_id'); - $user_first_name = $this->getAuthContextVar('user_first_name'); - $user_last_name = $this->getAuthContextVar('user_last_name'); - $user_email = $this->getAuthContextVar('user_email'); + $user_external_id = $this->getAuthContextVar('user_id'); + $user_first_name = $this->getAuthContextVar('user_first_name'); + $user_last_name = $this->getAuthContextVar('user_last_name'); + $user_email = $this->getAuthContextVar('user_email'); + $user_email_verified = boolval($this->getAuthContextVar('user_email_verified')); // at last resort try to get by email Log::debug(sprintf("ResourceServerContext::getCurrentUser getting user by email %s", $user_email)); $member = $this->member_repository->getByEmail($user_email); @@ -223,10 +226,15 @@ final class ResourceServerContext implements IResourceServerContext $member = $this->member_service->registerExternalUser ( - $user_external_id, - $user_email, - $user_first_name, - $user_last_name + new ExternalUserDTO + ( + $user_external_id, + $user_email, + $user_first_name, + $user_last_name, + true, + $user_email_verified + ) ); } @@ -237,6 +245,7 @@ final class ResourceServerContext implements IResourceServerContext if(!empty($user_last_name)) $member->setLastName($user_last_name); + $member->setEmailVerified(boolval($user_email_verified)); $member->setUserExternalId($user_external_id); } diff --git a/app/Models/ResourceServer/AccessTokenService.php b/app/Models/ResourceServer/AccessTokenService.php index f42683a9..c39af4a6 100644 --- a/app/Models/ResourceServer/AccessTokenService.php +++ b/app/Models/ResourceServer/AccessTokenService.php @@ -11,16 +11,18 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ + use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\Log; use libs\oauth2\InvalidGrantTypeException; use libs\oauth2\OAuth2InvalidIntrospectionResponse; use libs\oauth2\OAuth2Protocol; use libs\utils\ConfigurationException; use libs\utils\ICacheService; use models\oauth2\AccessToken; -use Illuminate\Support\Facades\Log; + /** * Class AccessTokenService * @package App\Models\ResourceServer @@ -44,6 +46,7 @@ final class AccessTokenService implements IAccessTokenService 'user_first_name', 'user_last_name', 'user_groups', + 'user_email_verified', ]; /** @@ -149,6 +152,10 @@ final class AccessTokenService implements IAccessTokenService $token_info['user_email'] = null; } + if(!array_key_exists("user_email_verified" , $token_info)){ + $token_info['user_email_verified'] = false; + } + if(!array_key_exists("user_first_name" , $token_info)){ $token_info['user_first_name'] = null; } diff --git a/app/Repositories/Main/DoctrineLegalDocumentRepository.php b/app/Repositories/Main/DoctrineLegalDocumentRepository.php index aaccc690..37621537 100644 --- a/app/Repositories/Main/DoctrineLegalDocumentRepository.php +++ b/app/Repositories/Main/DoctrineLegalDocumentRepository.php @@ -40,7 +40,7 @@ SQL; ]); $res = $stmt->fetchAll(); if(count($res) == 0 ) return null; - new LegalDocument( + return new LegalDocument( $res[0]['ID'], trim($res[0]['Title']), trim($res[0]['URLSegment']), @@ -70,7 +70,7 @@ SQL; ]); $res = $stmt->fetchAll(); if(count($res) == 0 ) return null; - new LegalDocument( + return new LegalDocument( $res[0]['ID'], trim($res[0]['Title']), trim($res[0]['URLSegment']), diff --git a/app/Services/Model/IMemberService.php b/app/Services/Model/IMemberService.php index e41834ee..5cf08519 100644 --- a/app/Services/Model/IMemberService.php +++ b/app/Services/Model/IMemberService.php @@ -12,10 +12,12 @@ * limitations under the License. **/ +use App\Services\Model\dto\ExternalUserDTO; use models\exceptions\EntityNotFoundException; use models\exceptions\ValidationException; use models\main\Affiliation; use models\main\Member; + /** * Interface IMemberService * @package App\Services\Model @@ -53,13 +55,10 @@ interface IMemberService public function deleteRSVP(Member $member, $rsvp_id); /** - * @param $user_external_id - * @param string $email - * @param string $first_name - * @param string $last_name + * @param ExternalUserDTO $userDTO * @return Member */ - public function registerExternalUser($user_external_id, string $email, string $first_name, string $last_name):Member; + public function registerExternalUser(ExternalUserDTO $userDTO):Member; /** * @param $user_external_id @@ -104,5 +103,11 @@ interface IMemberService * @throws EntityNotFoundException * @throws ValidationException */ - public function resignFoundationMembership(Member $member):Member; + public function signCommunityMembership(Member $member):Member; + + /** + * @param Member $member + * @return void + */ + public function resignMembership(Member $member); } \ No newline at end of file diff --git a/app/Services/Model/Imp/MemberService.php b/app/Services/Model/Imp/MemberService.php index 413fc96d..e8743ccc 100644 --- a/app/Services/Model/Imp/MemberService.php +++ b/app/Services/Model/Imp/MemberService.php @@ -16,6 +16,8 @@ use App\Events\NewMember; use App\Models\Foundation\Main\IGroup; use App\Models\Foundation\Main\Repositories\ILegalDocumentRepository; use App\Services\Apis\IExternalUserApi; +use App\Services\Model\dto\ExternalUserDTO; +use DateTime; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; use libs\utils\ICacheService; @@ -29,7 +31,6 @@ use models\main\IMemberRepository; use models\main\IOrganizationRepository; use models\main\LegalAgreement; use models\main\Member; -use DateTime; use models\main\Organization; use models\summit\ISpeakerRegistrationRequestRepository; @@ -268,23 +269,32 @@ final class MemberService } /** - * @param $user_external_id - * @param string $email - * @param string $first_name - * @param string $last_name + * @param ExternalUserDTO $userDTO * @return Member + * @throws \Exception */ - public function registerExternalUser($user_external_id, string $email, string $first_name, string $last_name): Member + public function registerExternalUser(ExternalUserDTO $userDTO): Member { - return $this->tx_service->transaction(function () use ($user_external_id, $email, $first_name, $last_name) { - Log::debug(sprintf("MemberService::registerExternalUser - user_external_id %s email %s first_name %s last_name %s", $user_external_id, $email, $first_name, $last_name)); + return $this->tx_service->transaction(function () use ($userDTO) { + Log::debug + ( + sprintf + ( + "MemberService::registerExternalUser - user_external_id %s email %s first_name %s last_name %s", + $userDTO->getId(), + $userDTO->getEmail(), + $userDTO->getFirstName(), + $userDTO->getLastName() + ) + ); + $member = new Member(); - $member->setActive(true); - $member->setEmailVerified(true); - $member->setEmail($email); - $member->setFirstName($first_name); - $member->setLastName($last_name); - $member->setUserExternalId($user_external_id); + $member->setActive($userDTO->isActive()); + $member->setEmailVerified( $userDTO->isEmailVerified()); + $member->setEmail($userDTO->getEmail()); + $member->setFirstName( $userDTO->getFirstName()); + $member->setLastName($userDTO->getLastName()); + $member->setUserExternalId($userDTO->getId()); $this->member_repository->add($member, true); Event::fire(new NewMember($member->getId())); return $member; @@ -299,6 +309,7 @@ final class MemberService public function registerExternalUserById($user_external_id): Member { return $this->tx_service->transaction(function () use ($user_external_id) { + // get external user from IDP $user_data = $this->user_ext_api->getUserById($user_external_id); $email = trim($user_data['email']); // first by external id due email could be updated @@ -313,8 +324,8 @@ final class MemberService if(is_null($member)) { Log::debug(sprintf("MemberService::registerExternalUserById %s does not exists , creating it ...", $email)); $member = new Member(); - $member->setActive(true); - $member->setEmailVerified(true); + $member->setActive(boolval($user_data['active'])); + $member->setEmailVerified(boolval($user_data['email_verified'])); $member->setEmail($email); $member->setFirstName(trim($user_data['first_name'])); $member->setLastName(trim($user_data['last_name'])); @@ -327,7 +338,8 @@ final class MemberService } else { Log::debug(sprintf("MemberService::registerExternalUserById %s already exists", $email)); - $member->setEmailVerified(true); + $member->setActive(boolval($user_data['active'])); + $member->setEmailVerified(boolval($user_data['email_verified'])); $member->setEmail($email); $member->setFirstName(trim($user_data['first_name'])); $member->setLastName(trim($user_data['last_name'])); @@ -529,7 +541,7 @@ final class MemberService /** * @inheritDoc */ - public function resignFoundationMembership(Member $member): Member + public function signCommunityMembership(Member $member): Member { return $this->tx_service->transaction(function() use($member){ if(!$member->isFoundationMember()) @@ -546,4 +558,18 @@ final class MemberService return $member; }); } + + /** + * @inheritDoc + */ + public function resignMembership(Member $member) + { + return $this->tx_service->transaction(function() use($member){ + + $member->resignMembership(); + + $this->member_repository->delete($member); + + }); + } } \ No newline at end of file diff --git a/app/Services/Model/Imp/SummitOrderService.php b/app/Services/Model/Imp/SummitOrderService.php index 6f28b5b5..4de7b072 100644 --- a/app/Services/Model/Imp/SummitOrderService.php +++ b/app/Services/Model/Imp/SummitOrderService.php @@ -26,6 +26,7 @@ use App\Models\Foundation\Summit\Factories\SummitOrderFactory; use App\Models\Foundation\Summit\Registration\IBuildDefaultPaymentGatewayProfileStrategy; use App\Models\Foundation\Summit\Repositories\ISummitAttendeeBadgePrintRuleRepository; use App\Models\Foundation\Summit\Repositories\ISummitAttendeeBadgeRepository; +use App\Services\Model\dto\ExternalUserDTO; use App\Services\Utils\CSVReader; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Event; @@ -1310,10 +1311,15 @@ final class SummitOrderService // we have an user on idp $member = $this->member_service->registerExternalUser ( - $user['id'], - $user['email'], - $user['first_name'], - $user['last_name'] + new ExternalUserDTO + ( + $user['id'], + $user['email'], + $user['first_name'], + $user['last_name'], + boolval($user['active']), + boolval($user['email_verified']) + ) ); // add the order to newly created member @@ -3334,10 +3340,15 @@ final class SummitOrderService // we have an user on idp $member = $this->member_service->registerExternalUser ( - $user['id'], - $user['email'], - $user['first_name'], - $user['last_name'] + new ExternalUserDTO + ( + $user['id'], + $user['email'], + $user['first_name'], + $user['last_name'], + boolval($user['active']), + boolval($user['email_verified']) + ) ); // add the order to newly created member $member->addSummitRegistrationOrder($order); diff --git a/app/Services/Model/Imp/SummitRegistrationInvitationService.php b/app/Services/Model/Imp/SummitRegistrationInvitationService.php index 8a0aad16..1f868a5e 100644 --- a/app/Services/Model/Imp/SummitRegistrationInvitationService.php +++ b/app/Services/Model/Imp/SummitRegistrationInvitationService.php @@ -18,6 +18,7 @@ use App\Models\Foundation\Summit\Factories\SummitRegistrationInvitationFactory; use App\Models\Foundation\Summit\Repositories\ISummitRegistrationInvitationRepository; use App\Services\Apis\IExternalUserApi; use App\Services\Model\AbstractService; +use App\Services\Model\dto\ExternalUserDTO; use App\Services\Model\IMemberService; use App\Services\Model\ISummitRegistrationInvitationService; use App\Services\Utils\CSVReader; @@ -241,10 +242,15 @@ final class SummitRegistrationInvitationService // we have an user on idp $member = $this->member_service->registerExternalUser ( - $user['id'], - $user['email'], - $user['first_name'], - $user['last_name'] + new ExternalUserDTO + ( + $user['id'], + $user['email'], + $user['first_name'], + $user['last_name'], + boolval($user['active']), + boolval($user['email_verified']) + ) ); } } diff --git a/app/Services/Model/dto/ExternalUserDTO.php b/app/Services/Model/dto/ExternalUserDTO.php new file mode 100644 index 00000000..5825b7e3 --- /dev/null +++ b/app/Services/Model/dto/ExternalUserDTO.php @@ -0,0 +1,116 @@ +id = $id; + $this->email = $email; + $this->first_name = $first_name; + $this->last_name = $last_name; + $this->active = $active; + $this->email_verified = $email_verified; + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getEmail(): ?string + { + return $this->email; + } + + /** + * @return string + */ + public function getFirstName(): ?string + { + return $this->first_name; + } + + /** + * @return string + */ + public function getLastName(): ?string + { + return $this->last_name; + } + + /** + * @return bool + */ + public function isActive(): bool + { + return $this->active; + } + + /** + * @return bool + */ + public function isEmailVerified(): bool + { + return $this->email_verified; + } + +} \ No newline at end of file diff --git a/database/seeds/ApiEndpointsSeeder.php b/database/seeds/ApiEndpointsSeeder.php index 7d4ac30e..bfd9686d 100644 --- a/database/seeds/ApiEndpointsSeeder.php +++ b/database/seeds/ApiEndpointsSeeder.php @@ -5451,11 +5451,17 @@ class ApiEndpointsSeeder extends Seeder 'scopes' => [sprintf(MemberScopes::WriteMyMemberData, $current_realm)], ], [ - 'name' => 'resign-foundation-membership', + 'name' => 'sign-community-membership', 'route' => '/api/v1/members/me/membership/community', 'http_method' => 'PUT', 'scopes' => [sprintf(MemberScopes::WriteMyMemberData, $current_realm)], ], + [ + 'name' => 'resign-membership', + 'route' => '/api/v1/members/me/membership/resign', + 'http_method' => 'DELETE', + 'scopes' => [sprintf(MemberScopes::WriteMyMemberData, $current_realm)], + ], // my member affiliations [ 'name' => 'get-my-member-affiliations',