Skip to content

Commit d5dca87

Browse files
committed
Account merging functionality
1 parent ce54101 commit d5dca87

5 files changed

Lines changed: 235 additions & 1 deletion

File tree

packages/security/GoogleUserDirectory.cfc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
<cfif structisempty(stUser)>
3535
<cfset stUser = oUser.getData(createuuid()) />
3636
<cfset stUser.userid = stTokenInfo.user_id />
37-
<cfset stUser.refreshToken = stTokens.refresh_token />
37+
<cfif structkeyexists(stTokens,"refresh_token")>
38+
<cfset stUser.refreshToken = stTokens.refresh_token />
39+
</cfif>
3840
<cfset stUser.providerDomain = listlast(session.security.ga[hash(stTokenInfo.user_id)].profile.email,"@") />
3941
<cfset oUser.setData(stProperties=stUser) />
4042
<cfelse>

packages/types/gudUser.cfc

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,168 @@
9999
<cfreturn stResult />
100100
</cffunction>
101101

102+
103+
<cffunction name="queryUserPassword" access="public" output="false" returntype="query" hint="Return a query of farUser rows that match the provided credentials">
104+
<cfargument name="username" type="string" required="true" />
105+
<cfargument name="password" type="string" required="true" />
106+
107+
<cfset var qUser = "" />
108+
<cfset var authenticatedObjectId = "" />
109+
<cfset var hashName = application.security.userdirectories.clientud.getOutputHashName() />
110+
111+
<!--- Find the user --->
112+
<cfquery datasource="#application.dsn#" name="qUser">
113+
select objectid,userid,password,userstatus
114+
from #application.dbowner#farUser
115+
where userid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(arguments.username)#" />
116+
</cfquery>
117+
118+
<!--- Try to match the entered password against the users in the DB --->
119+
<cfloop query="qUser">
120+
<cfif application.security.cryptlib.passwordMatchesHash(password=arguments.password,hashedPassword=qUser.password)>
121+
<cfset authenticatedObjectId = qUser.objectid />
122+
<cfbreak />
123+
</cfif>
124+
</cfloop>
125+
126+
<cfif Len(authenticatedObjectId)>
127+
<!--- Return the row with the password match --->
128+
<cfquery dbtype="query" name="qUser">
129+
select *
130+
from qUser
131+
where objectid = '#authenticatedObjectId#'
132+
</cfquery>
133+
<!--- Does the hashed password need to be updated? --->
134+
<cfif application.security.cryptlib.hashedPasswordIsStale(hashedPassword=qUser.password,password=arguments.password,hashname=hashName)>
135+
<cfquery datasource="#application.dsn#">
136+
update #application.dbowner#farUser
137+
set password=<cfqueryparam cfsqltype="cf_sql_varchar" value="#application.security.cryptlib.encodePassword(password=arguments.password,hashname=hashName)#" />
138+
where objectid=<cfqueryparam cfsqltype="cf_sql_varchar" value="#authenticatedObjectId#" />
139+
</cfquery>
140+
</cfif>
141+
<cfelse>
142+
<!--- Delete all rows from the query --->
143+
<cfquery dbtype="query" name="qUser">
144+
select *
145+
from qUser
146+
where 0 = 1
147+
</cfquery>
148+
</cfif>
149+
150+
<cfreturn qUser />
151+
</cffunction>
152+
153+
154+
<cffunction name="migrateContentUserData" access="private" output="false" returntype="void">
155+
<cfargument name="typename" type="string" required="true" />
156+
<cfargument name="oldprofile" type="struct" required="true" />
157+
<cfargument name="newprofile" type="struct" required="true" />
158+
159+
<cfset var property = "" />
160+
161+
<!--- Extended array component - skip --->
162+
<cfif find("_",arguments.typename)>
163+
<cfreturn />
164+
</cfif>
165+
166+
<!--- Undeployed - skip --->
167+
<cfif isdefined("application.fc.lib.db")>
168+
<cfif not application.fc.lib.db.isDeployed(arguments.typename)>
169+
<cfreturn />
170+
</cfif>
171+
<cfelse>
172+
<cfif not createObject("component", "farcry.core.packages.farcry.alterType").isCFCDeployed(arguments.typename)>
173+
<cfreturn />
174+
</cfif>
175+
</cfif>
176+
177+
<!--- Update user properties --->
178+
<cfloop list="createdby,lastupdatedby,lockedby" index="property">
179+
<cfif structkeyexists(application.stCOAPI[arguments.typename].stProps,property)>
180+
<cfquery datasource="#application.dsn#">
181+
update #application.dbowner##arguments.typename#
182+
set #property# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.newprofile.username#" />
183+
where #property# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.oldprofile.username#" />
184+
</cfquery>
185+
</cfif>
186+
</cfloop>
187+
188+
<!--- Update profile properties --->
189+
<cfloop list="ownedby" index="property">
190+
<cfif structkeyexists(application.stCOAPI[arguments.typename].stProps,property)>
191+
<cfquery datasource="#application.dsn#">
192+
update #application.dbowner##arguments.typename#
193+
set #property# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.newprofile.objectid#" />
194+
where #property# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.oldprofile.objectid#" />
195+
</cfquery>
196+
</cfif>
197+
</cfloop>
198+
199+
</cffunction>
200+
201+
<cffunction name="migrateLogs" access="private" output="false" returntype="void">
202+
<cfargument name="oldusername" type="string" required="true" />
203+
<cfargument name="newusername" type="string" required="true" />
204+
205+
<cfquery datasource="#application.dsn#">
206+
update farLog
207+
set userid = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.newusername#" />
208+
where userid = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.oldusername#" />
209+
and type in ('types','rules')
210+
</cfquery>
211+
</cffunction>
212+
213+
<cffunction name="migrateProfile" access="private" output="false" returntype="void">
214+
<cfargument name="oldprofile" type="struct" required="true" />
215+
<cfargument name="newprofile" type="struct" required="true" />
216+
217+
<cfset var property = "" />
218+
219+
<cfloop collection="#oldprofile#" item="property">
220+
<cfif listfindnocase("bReceiveEmail,phone,fax,position,department,locale,overviewhome,notes",property)>
221+
<cfset arguments.newprofile[property] = arguments.oldprofile[property] />
222+
</cfif>
223+
</cfloop>
224+
225+
<cfset application.fapi.setData(stProperties=arguments.newprofile) />
226+
<cfset session.dmProfile = arguments.newprofile />
227+
</cffunction>
228+
229+
<cffunction name="disableOldUser" access="private" output="false" returntype="void">
230+
<cfargument name="userid" type="string" required="true" />
231+
232+
<cfset var oUser = application.fapi.getContentType("farUser") />
233+
<cfset var stUser = oUser.getByUserID(arguments.userid) />
234+
235+
<cfset stUser.userstatus = "inactive" />
236+
237+
<cfset oUser.setData(stProperties=stUser) />
238+
</cffunction>
239+
240+
<cffunction name="mergeFarcryUDAccount" access="public" output="false" returntype="void">
241+
<cfargument name="userid" type="string" required="true" />
242+
<cfargument name="profileid" type="uuid" required="true" />
243+
244+
<cfset var typename = "" />
245+
<cfset var oProfile = application.fapi.getContentType("dmProfile") />
246+
<cfset var stOldProfile = oProfile.getProfile(arguments.userid,"CLIENTUD") />
247+
<cfset var stNewProfile = oProfile.getData(arguments.profileid) />
248+
249+
<!--- Update createdby,lastupdatedby,lockedby,ownedby --->
250+
<cfloop collection="#application.stCOAPI#" item="typename">
251+
<cfif listfindnocase("type,rule",application.stCOAPI[typename].class)>
252+
<cfset migratecontentuserdata(typename,stOldProfile,stNewProfile) />
253+
</cfif>
254+
</cfloop>
255+
256+
<!--- Update logs --->
257+
<cfset migrateLogs(stOldProfile.username,stNewProfile.username) />
258+
259+
<!--- Copy profile data --->
260+
<cfset migrateProfile(stOldProfile,stNewProfile) />
261+
262+
<!--- Disable old account --->
263+
<cfset disableOldUser(arguments.userid) />
264+
</cffunction>
265+
102266
</cfcomponent>

webskin/dmProfile/displaySummaryOptionsGUD.cfm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
<a href="https://www.google.com/settings/account">Google Account Settings</a>
1313
</small>
1414
</li>
15+
<li>
16+
<small>
17+
<a href="#application.fapi.getLink(type='gudLogin',view='displayPageAdmin',bodyView='displayTypeBodyMerge')#" target="content">Merge Existing Account</a>
18+
</small>
19+
</li>
1520
</cfoutput>
1621

1722
<cfsetting enablecfoutputonly="false" />
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<cfsetting enablecfoutputonly="true" />
2+
<!--- @@fualias: admin --->
3+
4+
<cfimport taglib="/farcry/core/tags/admin" prefix="admin" />
5+
<cfimport taglib="/farcry/core/tags/webskin" prefix="skin" />
6+
7+
<admin:header>
8+
9+
<skin:view stObject="#stObj#" webskin="#url.bodyView#" />
10+
11+
<admin:footer>
12+
13+
<cfsetting enablecfoutputonly="false" />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<cfsetting enablecfoutputonly="true" />
2+
<!--- @@displayname: Merge existing profile --->
3+
<!--- @@fuAlias: merge --->
4+
5+
<cfimport taglib="/farcry/core/tags/formtools" prefix="ft" />
6+
<cfimport taglib="/farcry/core/tags/webskin" prefix="skin" />
7+
8+
<ft:processform action="Merge">
9+
<ft:processformObjects typename="farLogin">
10+
<cfset oUser = application.fapi.getContentType("gudUser") />
11+
<cfset qUsers = oUser.queryUserPassword(stProperties.username,stProperties.password) />
12+
<cfif qUsers.recordcount>
13+
<cfset oUser.mergeFarcryUDAccount(qUsers.userid,session.dmProfile.objectid) />
14+
<skin:bubble message="The '#qUsers.userid#' FarCry account has been merged into this Google profile. You will no longer be able to log in with that username / password." />
15+
<cfelse>
16+
<skin:bubble message="No users match those credentials" tags="error" />
17+
</cfif>
18+
</ft:processformObjects>
19+
</ft:processform>
20+
21+
22+
<cfoutput>
23+
<h1>Merge Existing Account</h1>
24+
</cfoutput>
25+
26+
<cfif application.security.isLoggedIn() and refindnocase("_GUD$",session.dmProfile.username)>
27+
<cfoutput>
28+
<p>This utility merges an existing account into this one. Specifically it will:</p>
29+
<ul>
30+
<li>copy profile information (where it hasn't already been set)</li>
31+
<li>disable the username / password login</li>
32+
<li>update audit logs and history to the new account</li>
33+
</ul>
34+
</cfoutput>
35+
36+
<skin:pop tags="error" start="<ul class='error'>" end="</ul>"><cfoutput><li>#message.message#</li></cfoutput></skin:pop>
37+
<skin:pop start="<ul class='success'>" end="</ul>"><cfoutput><li>#message.message#</li></cfoutput></skin:pop>
38+
39+
<ft:form>
40+
<ft:object typename="farLogin" lFields="username,password" prefix="login" legend="" focusField="username" />
41+
42+
<ft:buttonPanel>
43+
<ft:button value="Merge" />
44+
</ft:buttonPanel>
45+
</ft:form>
46+
<cfelse>
47+
<cfoutput><ul id='errorMsg'><li>You must be logged in via Google to use this functionality</li></ul></cfoutput>
48+
</cfif>
49+
50+
<cfsetting enablecfoutputonly="false" />

0 commit comments

Comments
 (0)