1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.user.social
21 
22 import grizzled.slf4j.Logging
23 import org.make.api.operation.OperationServiceComponent
24 import org.make.api.technical.auth.{MakeDataHandlerComponent, TokenResponse}
25 import org.make.api.user.SocialProvider.{Facebook, GooglePeople, OpenIDConnect}
26 import org.make.api.user.social.models.UserInfo
27 import org.make.api.user.{SocialLoginResponse, SocialProvider, UserServiceComponent}
28 import org.make.core.RequestContext
29 import org.make.core.auth.{ClientId, UserRights}
30 import org.make.core.operation.{OperationId, SimpleOperation}
31 import org.make.core.question.QuestionId
32 import org.make.core.reference.{Country, Language}
33 import org.make.core.user.{User, UserId}
34 import scalaoauth2.provider.AuthInfo
35 
36 import java.time.ZonedDateTime
37 import scala.concurrent.ExecutionContext.Implicits.global
38 import scala.concurrent.Future
39 
40 trait SocialServiceComponent extends GoogleApiComponent with FacebookApiComponent with OpenIDConnectApiComponent {
41   def socialService: SocialService
42 }
43 
44 trait SocialService {
45 
46   def login(
47     provider: SocialProvider,
48     token: String,
49     country: Country,
50     language: Language,
51     crmCountry: Country,
52     crmLanguage: Language,
53     questionId: Option[QuestionId],
54     requestContext: RequestContext,
55     validatedClientId: ClientId,
56     privacyPolicyApprovalDate: Option[ZonedDateTime],
57     optIn: Option[Boolean],
58     operationId: Option[OperationId],
59     redirectUri: Option[String]
60   ): Future[(UserId, SocialLoginResponse)]
61   def getUserByProviderAndToken(
62     provider: SocialProvider,
63     token: String,
64     operationId: Option[OperationId],
65     redirectUri: Option[String]
66   ): Future[Option[User]]
67 }
68 
69 trait DefaultSocialServiceComponent extends SocialServiceComponent {
70   self: UserServiceComponent with OperationServiceComponent with MakeDataHandlerComponent with Logging =>
71 
72   override lazy val socialService: SocialService = new DefaultSocialService
73 
74   class DefaultSocialService extends SocialService {
75 
76     private def getUserInfo(
77       provider: SocialProvider,
78       token: String,
79       operation: Option[SimpleOperation],
80       redirectUri: Option[String]
81     ): Future[UserInfo] = {
82       provider match {
83         case GooglePeople => googleApi.peopleInfo(token).map(_.toUserInfo())
84         case Facebook     => facebookApi.getUserInfo(token).map(_.toUserInfo())
85         case OpenIDConnect =>
86           (operation.map(_.operationId), operation.flatMap(_.operationAuthentication).flatMap(_.oidc), redirectUri) match {
87             case (Some(id), Some(oidc), Some(redirectUri)) =>
88               openIDConnectApi.getUserInfo(token, oidc, redirectUri).map(_.toUserInfo(id))
89             case _ => Future.failed(SocialProviderException("No OIDC configuration found"))
90           }
91       }
92     }
93 
94     override def login(
95       provider: SocialProvider,
96       token: String,
97       country: Country,
98       language: Language,
99       crmCountry: Country,
100       crmLanguage: Language,
101       questionId: Option[QuestionId],
102       requestContext: RequestContext,
103       validatedClientId: ClientId,
104       privacyPolicyApprovalDate: Option[ZonedDateTime],
105       optIn: Option[Boolean],
106       operationId: Option[OperationId],
107       redirectUri: Option[String]
108     ): Future[(UserId, SocialLoginResponse)] = {
109 
110       for {
111         operation <- operationId match {
112           case Some(id) => operationService.findOneSimple(id)
113           case None     => Future.successful(None)
114         }
115         userInfo <- getUserInfo(provider, token, operation, redirectUri)
116         (user, accountCreation) <- userService.createOrUpdateUserFromSocial(
117           userInfo,
118           questionId,
119           country,
120           language,
121           crmCountry,
122           crmLanguage,
123           requestContext,
124           privacyPolicyApprovalDate,
125           optIn
126         )
127         accessToken <- oauth2DataHandler.createAccessToken(authInfo = AuthInfo(
128           user = UserRights(user.userId, user.roles, user.availableQuestions, user.emailVerified),
129           clientId = Some(validatedClientId.value),
130           scope = None,
131           redirectUri = None
132         )
133         )
134       } yield {
135         (
136           user.userId,
137           SocialLoginResponse(token = TokenResponse.fromAccessToken(accessToken), accountCreation = accountCreation)
138         )
139       }
140     }
141 
142     override def getUserByProviderAndToken(
143       provider: SocialProvider,
144       token: String,
145       operationId: Option[OperationId],
146       redirectUri: Option[String]
147     ): Future[Option[User]] = {
148       val operation = operationId match {
149         case Some(id) => operationService.findOneSimple(id)
150         case None     => Future.successful(None)
151       }
152       val userEmail: Future[Option[String]] =
153         operation.flatMap(getUserInfo(provider, token, _, redirectUri).map(_.email))
154       userEmail.flatMap {
155         case None        => Future.successful(None)
156         case Some(email) => userService.getUserByEmail(email)
157       }
158     }
159 
160   }
161 }
162 
163 final case class SocialProviderException(message: String) extends Exception(message)
Line Stmt Id Pos Tree Symbol Tests Code
83 43384 2956 - 3003 ApplyToImplicitArgs scala.concurrent.Future.map DefaultSocialServiceComponent.this.googleApi.peopleInfo(token).map[org.make.api.user.social.models.UserInfo](((x$1: org.make.api.user.social.models.google.PeopleInfo) => x$1.toUserInfo()))(scala.concurrent.ExecutionContext.Implicits.global)
83 33458 2988 - 3002 Apply org.make.api.user.social.models.google.PeopleInfo.toUserInfo x$1.toUserInfo()
83 50957 2987 - 2987 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
84 34558 3068 - 3082 Apply org.make.api.user.social.models.facebook.UserInfo.toUserInfo x$2.toUserInfo()
84 39986 3033 - 3083 ApplyToImplicitArgs scala.concurrent.Future.map DefaultSocialServiceComponent.this.facebookApi.getUserInfo(token).map[org.make.api.user.social.models.UserInfo](((x$2: org.make.api.user.social.models.facebook.UserInfo) => x$2.toUserInfo()))(scala.concurrent.ExecutionContext.Implicits.global)
84 47573 3067 - 3067 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
88 49915 3372 - 3372 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
88 41092 3314 - 3390 ApplyToImplicitArgs scala.concurrent.Future.map DefaultSocialServiceComponent.this.openIDConnectApi.getUserInfo(token, oidc, redirectUri).map[org.make.api.user.social.models.UserInfo](((x$6: org.make.api.user.social.models.oidc.UserInfo) => x$6.toUserInfo(id)))(scala.concurrent.ExecutionContext.Implicits.global)
88 36617 3373 - 3389 Apply org.make.api.user.social.models.oidc.UserInfo.toUserInfo x$6.toUserInfo(id)
89 33206 3427 - 3481 Apply org.make.api.user.social.SocialProviderException.apply SocialProviderException.apply("No OIDC configuration found")
89 50997 3413 - 3482 Apply scala.concurrent.Future.failed scala.concurrent.Future.failed[Nothing](SocialProviderException.apply("No OIDC configuration found"))
111 34839 4020 - 5034 ApplyToImplicitArgs scala.concurrent.Future.flatMap org.make.api.user.social.socialservicecomponenttest operationId match { case (value: org.make.core.operation.OperationId): Some[org.make.core.operation.OperationId]((id @ _)) => DefaultSocialServiceComponent.this.operationService.findOneSimple(id) case scala.None => scala.concurrent.Future.successful[None.type](scala.None) }.flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((operation: Option[org.make.core.operation.SimpleOperation]) => DefaultSocialService.this.getUserInfo(provider, token, operation, redirectUri).flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((userInfo: org.make.api.user.social.models.UserInfo) => DefaultSocialServiceComponent.this.userService.createOrUpdateUserFromSocial(userInfo, questionId, country, language, crmCountry, crmLanguage, requestContext, privacyPolicyApprovalDate, optIn).withFilter(((check$ifrefutable$1: (org.make.core.user.User, Boolean)) => (check$ifrefutable$1: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => true case _ => false }))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((x$7: (org.make.core.user.User, Boolean)) => (x$7: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => DefaultSocialServiceComponent.this.oauth2DataHandler.createAccessToken(scalaoauth2.provider.AuthInfo.apply[org.make.core.auth.UserRights](org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5), scala.Some.apply[String](validatedClientId.value), scala.None, scala.None, scalaoauth2.provider.AuthInfo.apply$default$5[Nothing], scalaoauth2.provider.AuthInfo.apply$default$6[Nothing])).map[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((accessToken: scalaoauth2.provider.AccessToken) => scala.Tuple2.apply[org.make.core.user.UserId, org.make.api.user.SocialLoginResponse](user.userId, org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation))))(scala.concurrent.ExecutionContext.Implicits.global) }))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)
111 42419 4044 - 4044 Select scala.concurrent.ExecutionContext.Implicits.global org.make.api.user.social.socialservicecomponenttest scala.concurrent.ExecutionContext.Implicits.global
112 43135 4094 - 4128 Apply org.make.api.operation.OperationService.findOneSimple org.make.api.user.social.socialservicecomponenttest DefaultSocialServiceComponent.this.operationService.findOneSimple(id)
113 35007 4174 - 4178 Select scala.None org.make.api.user.social.socialservicecomponenttest scala.None
113 47611 4156 - 4179 Apply scala.concurrent.Future.successful org.make.api.user.social.socialservicecomponenttest scala.concurrent.Future.successful[None.type](scala.None)
115 46506 4198 - 5034 ApplyToImplicitArgs scala.concurrent.Future.flatMap DefaultSocialService.this.getUserInfo(provider, token, operation, redirectUri).flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((userInfo: org.make.api.user.social.models.UserInfo) => DefaultSocialServiceComponent.this.userService.createOrUpdateUserFromSocial(userInfo, questionId, country, language, crmCountry, crmLanguage, requestContext, privacyPolicyApprovalDate, optIn).withFilter(((check$ifrefutable$1: (org.make.core.user.User, Boolean)) => (check$ifrefutable$1: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => true case _ => false }))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((x$7: (org.make.core.user.User, Boolean)) => (x$7: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => DefaultSocialServiceComponent.this.oauth2DataHandler.createAccessToken(scalaoauth2.provider.AuthInfo.apply[org.make.core.auth.UserRights](org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5), scala.Some.apply[String](validatedClientId.value), scala.None, scala.None, scalaoauth2.provider.AuthInfo.apply$default$5[Nothing], scalaoauth2.provider.AuthInfo.apply$default$6[Nothing])).map[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((accessToken: scalaoauth2.provider.AccessToken) => scala.Tuple2.apply[org.make.core.user.UserId, org.make.api.user.SocialLoginResponse](user.userId, org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation))))(scala.concurrent.ExecutionContext.Implicits.global) }))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)
115 33197 4207 - 4207 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
116 49187 4295 - 4295 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
116 40511 4338 - 4338 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
116 41339 4271 - 5034 ApplyToImplicitArgs scala.concurrent.Future.flatMap DefaultSocialServiceComponent.this.userService.createOrUpdateUserFromSocial(userInfo, questionId, country, language, crmCountry, crmLanguage, requestContext, privacyPolicyApprovalDate, optIn).withFilter(((check$ifrefutable$1: (org.make.core.user.User, Boolean)) => (check$ifrefutable$1: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => true case _ => false }))(scala.concurrent.ExecutionContext.Implicits.global).flatMap[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((x$7: (org.make.core.user.User, Boolean)) => (x$7: (org.make.core.user.User, Boolean) @unchecked) match { case (_1: org.make.core.user.User, _2: Boolean): (org.make.core.user.User, Boolean)((user @ _), (accountCreation @ _)) => DefaultSocialServiceComponent.this.oauth2DataHandler.createAccessToken(scalaoauth2.provider.AuthInfo.apply[org.make.core.auth.UserRights](org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5), scala.Some.apply[String](validatedClientId.value), scala.None, scala.None, scalaoauth2.provider.AuthInfo.apply$default$5[Nothing], scalaoauth2.provider.AuthInfo.apply$default$6[Nothing])).map[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((accessToken: scalaoauth2.provider.AccessToken) => scala.Tuple2.apply[org.make.core.user.UserId, org.make.api.user.SocialLoginResponse](user.userId, org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation))))(scala.concurrent.ExecutionContext.Implicits.global) }))(scala.concurrent.ExecutionContext.Implicits.global)
127 36451 4563 - 5034 ApplyToImplicitArgs scala.concurrent.Future.map DefaultSocialServiceComponent.this.oauth2DataHandler.createAccessToken(scalaoauth2.provider.AuthInfo.apply[org.make.core.auth.UserRights](org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5), scala.Some.apply[String](validatedClientId.value), scala.None, scala.None, scalaoauth2.provider.AuthInfo.apply$default$5[Nothing], scalaoauth2.provider.AuthInfo.apply$default$6[Nothing])).map[(org.make.core.user.UserId, org.make.api.user.SocialLoginResponse)](((accessToken: scalaoauth2.provider.AccessToken) => scala.Tuple2.apply[org.make.core.user.UserId, org.make.api.user.SocialLoginResponse](user.userId, org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation))))(scala.concurrent.ExecutionContext.Implicits.global)
127 39701 4575 - 4575 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
127 41582 4625 - 4625 TypeApply scalaoauth2.provider.AuthInfo.apply$default$6 scalaoauth2.provider.AuthInfo.apply$default$6[Nothing]
127 49151 4625 - 4625 TypeApply scalaoauth2.provider.AuthInfo.apply$default$5 scalaoauth2.provider.AuthInfo.apply$default$5[Nothing]
127 33157 4625 - 4848 Apply scalaoauth2.provider.AuthInfo.apply scalaoauth2.provider.AuthInfo.apply[org.make.core.auth.UserRights](org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5), scala.Some.apply[String](validatedClientId.value), scala.None, scala.None, scalaoauth2.provider.AuthInfo.apply$default$5[Nothing], scalaoauth2.provider.AuthInfo.apply$default$6[Nothing])
128 41542 4688 - 4711 Select org.make.core.user.User.availableQuestions user.availableQuestions
128 33248 4713 - 4731 Select org.make.core.user.User.emailVerified user.emailVerified
128 43174 4652 - 4732 Apply org.make.core.auth.UserRights.apply org.make.core.auth.UserRights.apply(user.userId, user.roles, user.availableQuestions, user.emailVerified, org.make.core.auth.UserRights.apply$default$5)
128 36657 4663 - 4674 Select org.make.core.user.User.userId user.userId
128 47025 4652 - 4652 Select org.make.core.auth.UserRights.apply$default$5 org.make.core.auth.UserRights.apply$default$5
128 49396 4676 - 4686 Select org.make.core.user.User.roles user.roles
129 35045 4760 - 4783 Select org.make.core.auth.ClientId.value validatedClientId.value
129 48075 4755 - 4784 Apply scala.Some.apply scala.Some.apply[String](validatedClientId.value)
130 40549 4804 - 4808 Select scala.None scala.None
131 36697 4834 - 4838 Select scala.None scala.None
135 48115 4883 - 5034 Apply scala.Tuple2.apply scala.Tuple2.apply[org.make.core.user.UserId, org.make.api.user.SocialLoginResponse](user.userId, org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation))
136 47060 4895 - 4906 Select org.make.core.user.User.userId user.userId
137 34798 4918 - 5024 Apply org.make.api.user.SocialLoginResponse.apply org.make.api.user.SocialLoginResponse.apply(org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken), accountCreation)
137 43215 4946 - 4988 Apply org.make.api.technical.auth.TokenResponse.fromAccessToken org.make.api.technical.auth.TokenResponse.fromAccessToken(accessToken)
149 48628 5320 - 5354 Apply org.make.api.operation.OperationService.findOneSimple DefaultSocialServiceComponent.this.operationService.findOneSimple(id)
150 32139 5380 - 5403 Apply scala.concurrent.Future.successful scala.concurrent.Future.successful[None.type](scala.None)
150 39740 5398 - 5402 Select scala.None scala.None
153 33240 5484 - 5541 ApplyToImplicitArgs scala.concurrent.Future.map DefaultSocialService.this.getUserInfo(provider, token, x$8, redirectUri).map[Option[String]](((x$9: org.make.api.user.social.models.UserInfo) => x$9.email))(scala.concurrent.ExecutionContext.Implicits.global)
153 46261 5483 - 5483 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
153 48955 5533 - 5540 Select org.make.api.user.social.models.UserInfo.email x$9.email
153 41376 5532 - 5532 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
153 43170 5466 - 5542 ApplyToImplicitArgs scala.concurrent.Future.flatMap operation.flatMap[Option[String]](((x$8: Option[org.make.core.operation.SimpleOperation]) => DefaultSocialService.this.getUserInfo(provider, token, x$8, redirectUri).map[Option[String]](((x$9: org.make.api.user.social.models.UserInfo) => x$9.email))(scala.concurrent.ExecutionContext.Implicits.global)))(scala.concurrent.ExecutionContext.Implicits.global)
154 32651 5567 - 5567 Select scala.concurrent.ExecutionContext.Implicits.global scala.concurrent.ExecutionContext.Implicits.global
154 49695 5549 - 5690 ApplyToImplicitArgs scala.concurrent.Future.flatMap userEmail.flatMap[Option[org.make.core.user.User]](((x0$1: Option[String]) => x0$1 match { case scala.None => scala.concurrent.Future.successful[None.type](scala.None) case (value: String): Some[String]((email @ _)) => DefaultSocialServiceComponent.this.userService.getUserByEmail(email) }))(scala.concurrent.ExecutionContext.Implicits.global)
155 34600 5615 - 5619 Select scala.None scala.None
155 48659 5597 - 5620 Apply scala.concurrent.Future.successful scala.concurrent.Future.successful[None.type](scala.None)
156 39777 5649 - 5682 Apply org.make.api.user.UserService.getUserByEmail DefaultSocialServiceComponent.this.userService.getUserByEmail(email)