Best way to store permissions for the user account?

Multi tool use
Multi tool use
The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP


Best way to store permissions for the user account?



I have permission records that are tied to each account in my application. Each account can have one or multiple permission records based on account type. Here is example:


<cfquery name="qryUserPerm" datasource="#Application.dsn#">
SELECT AccessType, AccessLevel, State, City, Building
FROM Permissions
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(session.AccountID)#">
</cfquery>



Query above will produce data like this for one of the accounts:


RecID AccountID AccessType AccessLevel State City Building
70 285A637D82B9 F B NY New York 8010
71 285A637D82B9 F B NY New York 5412
73 285A637D82B9 F B NY New York 6103
74 285A637D82B9 F B NY New York 3106



As you can see above this account have 4 records assigned to them. Access Type can be Full F or View Only V. Access Level can be State 'S', City 'C' or Building 'B'. User can have only one access level assigned to them at the time, so for example there is no situation where user can have assigned City and State level. My question is what would be the best way to organize the data from the query for specific access level? In this case I have to merge 4 records in list or array. State level only can have one permission record assigned, City and Building can have multiple records. Here is example of what I have:


F


V


<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = "">

<cfloop query="qryUserPerm">
<cfif qryUserPerm.AccessLevel EQ "S">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = listAppend(permissionList, "", ",")>
<cfelseif qryUserPerm.AccessLevel EQ "C">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = listAppend(permissionList, qryUserPerm.City, ",")>
<cfelseif qryUserPerm.AccessLevel EQ "B">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = listAppend(permissionList, qryUserPerm.Building, ",")>
<cfelse>
<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = listAppend(permissionList, "", ",")>
</cfif>
</cfloop>



It seems redundant to keep permissionType and permissionLevel inside of the loop but I do not know better way currently to avoid that. Also this makes process very dificult in case when I have to compare permission list. I would have to run this same process and build the list in order to compare that with Session.premissionList in case where currently logged user change their permissions. Is there any way to merge these records with SQL? Or this approach is better option?


permissionType


permissionLevel


Session.premissionList





How are you using the values in the application? Re: "in case where currently logged user change their permissions" Why would you need to compare, instead of just wiping out the old permissions and storing the new ones?
– Ageax
20 hours ago





@Ageax that is an option as well. I was thinking there might be some scenario where I have to compare the lists before I run the update.
– espresso_coffee
20 hours ago





Comparisons could probably be done in SQL, but delete/replace all is more typical. If you are only returning city and building values, why not do that in SQL? (For that matter, you could build the list in SQL, though just as easy to use CF).
– Ageax
18 hours ago







@Ageax Any example on how to achieve that in SQL?
– espresso_coffee
18 hours ago





@espresso_coffee - I noticed your other thread includes "State" when AccessLevel = "S", this one thread doesn't. So I added "state" to my previous answer below.
– Ageax
1 hour ago




3 Answers
3



I would be tempted to remove the loop. I am thinking that this may make things a little simpler.


<cfset local.permissionType = "">
<cfset local.permissionLevel = "">
<cfset local.permissionList = "">


<cfif qryUserPerm.AccessLevel EQ "S">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = qryUserPerm.State>
<cfelseif qryUserPerm.AccessLevel EQ "C">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.City))>
<cfelseif qryUserPerm.AccessLevel EQ "B">
<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = ListRemoveDuplicates(ValueList(permissionList,qryUserPerm.Building))>
</cfif>



And, if you want to compare the lists in future for equality, you may want to use:


<cfset local.permissionList = ListSort(local.permissionList,"textnocase","asc")>





So you are saying that loop is not necessary and this can be achieved with valueList() ?
– espresso_coffee
21 hours ago


valueList()





@expresso_coffee Well, in this case, it looks like the only column values that change are RecID, City & Building, so you can collect these different values in ValueList()
– Charles Robertson
20 hours ago





Can you please provide example with cfscript? I'm trying to get this to work with cfscript.
– espresso_coffee
4 hours ago


cfscript



You could trim down the code by using CASE to merge everything into a single column, based on the Access Level.


SELECT AccessType
, AccessLevel
, CASE AccessLevel
WHEN 'C' THEN City
WHEN 'B' THEN Building
WHEN 'S' THEN State
END AS AccessValue
FROM Permissions
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#session.AccountID#">



Then build your list from that column. No cfif's needed.


<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = valueList(qryUserPerm.AccessValue)>



You could also build the CSV list in SQL only, but not sure it's worth it in this scenario, since it's just as easy to build in CF.


SELECT TOP 1 AccessType
, AccessLevel
, STUFF(( SELECT ','+ l.AccessValue
FROM ( SELECT CASE AccessLevel
WHEN 'C' THEN City
WHEN 'B' THEN Building
WHEN 'S' THEN State
END AS AccessValue
FROM Permissions l
WHERE l.AccountID = p.AccountID
) l
GROUP BY l.AccessValue
FOR XML PATH('')
),1,1,'') AS PermissionsList
FROM Permissions p
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#session.AccountID#">



Anyway, using the above the query will return everything you need in a single row: AccessType, AccessLevel and csv list of permissions.


<cfset local.permissionType = qryUserPerm.AccessType>
<cfset local.permissionLevel = qryUserPerm.AccessLevel>
<cfset local.permissionList = qryUserPerm.PermissionsList>



This can be done in SQL itself, which may be more performant than manipulating the data in code.



One issue with the data is that the State, City and Building columns need to be unpivoted to then be turned into a comma-delimited list.


State


City


Building



Since you are using SQL 2008, you have access to the functionality you need.



The query is: http://sqlfiddle.com/#!18/0f4f7/1


; WITH cte AS (
SELECT
AccountID, AccessType, AccessLevel
, CASE AccessLevel
WHEN 'S' THEN State
WHEN 'C' THEN City
WHEN 'B' THEN Building
END AS Permissions
FROM Permissions
WHERE AccountID =
<cfqueryparam cfsqltype="cf_sql_integer" value="#session.AccountID#">
/* Dynamic variable here */
)
SELECT DISTINCT AccountID, AccessType, AccessLevel
, CASE
WHEN AccessLevel = 'S' THEN Permissions
ELSE LEFT(ca.pl, COALESCE(LEN(ca.pl),0)-1)
END AS PermissionList
FROM cte
CROSS APPLY (
SELECT p.Permissions + ', '
FROM cte p
WHERE p.AccountID = cte.AccountID
AND p.AccessType = cte.AccessType
AND p.AccessLevel = cte.AccessLevel
FOR XML PATH('')
) ca (pl) ;



I start with a CTE to build out the "unpivoted" list of Permissions based on the AccessLevel. If this can be put in a SQL View, you can just leave out the WHERE statement here and just call it when you call the View. A View would be my preference, if you can get it into your database.


Permissions


AccessLevel


WHERE



After I have the CTE, I just select the base columns (AccountID, AccessType and AccessLevel, and then I CROSS APPLY a comma-delimited list of the Permissions. I use FOR XML PATH('') to build that comma-delimited list.


AccountID


AccessType


AccessLevel


CROSS APPLY


Permissions


FOR XML PATH('')



If this is able to be converted to a View, it would be a simple


<cfquery name="qryUserPerm" datasource="#Application.dsn#">
SELECT AccessType, AccessLevel, PermissionList
FROM myPermissionsView
WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(session.AccountID)#">
</cfquery>



If not, you'll have to try running the above full query inside the cfquery tag.


cfquery



This should give you back a dataset like:


| AccessType | AccessLevel | PermissionList |
|------------|-------------|------------------------|
| F | B | 8010, 5412, 6103, 3106 |



You only have one result to work with and won't have to loop.





Thanks for getting back and providing example. So ideal solution would be to create View that would look like first query in your answer where you organize Permissions with case statement? Then call the view in final query? Let me know if this is correct or I misunderstood your answer.
– espresso_coffee
1 hour ago


View


case


view





A View would be my suggestion. But I prefer to do data manipulation as close to the source as possible; which usually means SQL. The more I've worked with SQL, the more I've realized that I was beating up on both my database and my network when I was doing stuff in code. Granted, like all other IT things, that depends. If you're working with thousands of users a minute, then it may be better to spread the data manipulation load across app servers rather than database. You'll have to load test.
– Shawn
1 hour ago





The CTE in the View would not contain WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#session.AccountID#"> . That would go in the cfquery. You're filtering a larger dataset there, but the View will be cachable on the SQL Server. Even with a ton of rows, it should still be pretty efficient.
– Shawn
1 hour ago




WHERE AccountID = <cfqueryparam cfsqltype="cf_sql_integer" value="#session.AccountID#">


cfquery





I'm not sure what your data looks like, but in my SQLFiddle, I added a few different cases to show how it would return different types of data.
– Shawn
1 hour ago





I more kind of leaning to solution that @Charles Roberts provided. That way I'm putting more work on ColdFusion instead of my database. Again this is something that I'm not 100% sure about.
– espresso_coffee
1 hour ago






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

x1p akQ3Iz sHI,i,vJSuIW o,yxSmm4o9NXQEMMcm gLwZaLF 6Be6PJ3l5kq,i8PuP
vSI0bvs4CRCIbYGX6 g hsLZRF4s0u gjKwTbfUno1fRznVuN1,iMvalQggj RR a0 p7iye,r

Popular posts from this blog

Makefile test if variable is not empty

Will Oldham

Visual Studio Code: How to configure includePath for better IntelliSense results