El manejo de la información hoy en día es importante, pero lo es aún más la forma en que se controlan los accesos a la misma.
Hoy veremos como crear reglas de accesso a nuestras aplicaciones web basadas en asp.net utilizando el membership provider que nos proporciona asp.net de una forma fácil y rápida.
Para empezar, agregamos una nueva página a nuestro proyecto asp.net y definimos lo siguiente
<table class="webparts">
<tr>
<th>
Website Access Rules</th>
</tr>
<tr>
<td class="details" valign="top">
<p>
Use this page to manage access rules for your Web site. Rules are applied to
folders, thus providing robust folder-level security enforced by the ASP.NET
infrastructure. Rules are persisted as XML in each folder's Web.config file. <i>
Page-level security and inner-page security are not handled using this tool —
they are handled using specialized code that is available to the Web Developers.</i>
</p>
<table>
<tr>
<td style="padding-right: 30px; width: 78px;" valign="top">
<asp:Label ID="lblsoftestructure" Text="Current System Estructure" runat="server" />
<div class="treeview">
<asp:TreeView ID="FolderTree" runat="server"
OnSelectedNodeChanged="FolderTree_SelectedNodeChanged" ImageSet="Arrows">
<RootNodeStyle ImageUrl="~/Styles/img/folder.gif" />
<ParentNodeStyle ImageUrl="~/Styles/img/folder.gif" Font-Bold="False" />
<NodeStyle Font-Names="Tahoma" Font-Size="10pt" ForeColor="Black"
HorizontalPadding="5px" NodeSpacing="0px" VerticalPadding="0px" />
<LeafNodeStyle ImageUrl="~/Styles/img/folder.gif" />
<HoverNodeStyle Font-Underline="True" ForeColor="#5555DD" />
<SelectedNodeStyle Font-Underline="true" ForeColor="#5555DD"
HorizontalPadding="0px" VerticalPadding="0px" />
</asp:TreeView>
</div>
</td>
<td style="padding-left: 30px; border-left: 1px solid #999;" valign="top">
<asp:Panel ID="SecurityInfoSection" runat="server" Visible="false">
<h2 ID="TitleOne" runat="server" class="alert">
</h2>
<p>
Rules are applied in order. The first rule that matches applies, and the
permission in each rule overrides the permissions in all following rules. Use
the Move Up and Move Down buttons to change the order of the selected rule.
Rules that appear dimmed are inherited from the parent and cannot be changed at
this level.
</p>
<table style="width: 100%">
<tr>
<td style="width: 259px" class="style2">
</td>
<td style="width: 541px">
<asp:GridView ID="RulesGrid" runat="server" AutoGenerateColumns="false"
CssClass="GridViewStyle" GridLines="none" OnRowDataBound="RowDataBound">
<Columns>
<asp:TemplateField HeaderText="Action">
<ItemTemplate>
<%#CType(Container.DataItem, AuthorizationRule).Action%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Roles">
<ItemTemplate>
<%#CType(Container.DataItem, AuthorizationRule).Action%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="User">
<ItemTemplate>
<%#CType(Container.DataItem, AuthorizationRule).Action%>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Delete Rule">
<ItemTemplate>
<asp:Button ID="Button1" runat="server"
CommandArgument="<%# CType(Container.DataItem, AuthorizationRule) %>"
OnClick="DeleteRule"
OnClientClick="return confirm('Click OK to delete this rule.')"
Text="Delete Rule" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Move Rule">
<ItemTemplate>
<asp:Button ID="Button2" runat="server"
CommandArgument="<%# CType(Container.DataItem, AuthorizationRule) %>"
OnClick="MoveUp" Text=" Up " />
<asp:Button ID="Button3" runat="server"
CommandArgument="<%# CType(Container.DataItem, AuthorizationRule)%>"
OnClick="MoveDown" Text="Down" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<RowStyle CssClass="RowStyle" />
<EmptyDataRowStyle CssClass="EmptyRowStyle" />
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<PagerStyle CssClass="PagerStyle" />
<SelectedRowStyle CssClass="SelectedRowStyle" />
<HeaderStyle CssClass="HeaderStyle" />
<EditRowStyle CssClass="EditRowStyle" />
<AlternatingRowStyle CssClass="AltRowStyle" />
</asp:GridView>
</td>
<td>
</td>
</tr>
</table>
<br />
<hr />
<h2 ID="TitleTwo" runat="server" class="alert">
</h2>
<b>Action:</b>
<asp:RadioButton ID="ActionDeny" runat="server" Checked="true"
GroupName="action" Text="Deny" />
<asp:RadioButton ID="ActionAllow" runat="server" GroupName="action"
Text="Allow" />
<br />
<br />
<b>Rule applies to:</b>
<br />
<asp:RadioButton ID="ApplyRole" runat="server" Checked="true"
GroupName="applyto" Text="This Role:" />
<asp:DropDownList ID="UserRoles" runat="server" AppendDataBoundItems="true">
<asp:ListItem>Select Role</asp:ListItem>
</asp:DropDownList>
<br />
<asp:RadioButton ID="ApplyUser" runat="server" GroupName="applyto"
Text="This User:" />
<asp:DropDownList ID="UserList" runat="server" AppendDataBoundItems="true">
<asp:ListItem>Select User</asp:ListItem>
</asp:DropDownList>
<br />
<asp:RadioButton ID="ApplyAllUsers" runat="server" GroupName="applyto"
Text="All Users (*)" />
<br />
<asp:RadioButton ID="ApplyAnonUser" runat="server" GroupName="applyto"
Text="Anonymous Users (?)" />
<br />
<br />
<asp:Button ID="Button4" runat="server" OnClick="CreateRule"
OnClientClick="return confirm('Click OK to create this rule.');"
Text="Create Rule" />
<asp:Literal ID="RuleCreationError" runat="server"></asp:Literal>
</asp:Panel>
</td>
</tr>
</table>
</td>
</tr>
</table>
En el código anterior hemos echo varias cosas:
La primera, hemos declarado y creado un TreeView, que nos servirá para visualizar toda la estructura de directorios de nuestra aplicación.
La segunda hemos creado un gridview que utilizaremos para mostrar los permisos que tiene un rol en un determinado directorio.
Y por ultimo, hemos creado la estructura que nos permitirá seleccionar los roles, darles o denegarles acceso y atributos.
Ahora, veamos el Code Behind.
Declaramos dos variables
Private Const VirtualImageRoot As String = "~/"
Private selectedFolderName As String
Declaramos el procedimiento, addrule. Que nos permitirá agregar reglas a cada uno de los folders.
Protected Sub AddRule(ByVal newRule As AuthorizationRule)
Dim virtualFolderPath As String = FolderTree.SelectedValue
Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)
section.Rules.Add(newRule)
Try
config.Save()
RuleCreationError.Visible = False
Catch ex As Exception
RuleCreationError.Visible = True
RuleCreationError.Text = "<div class=""alert""><br />An error occurred and the rule was not added. I saw this happen during testing when I attempted to create a rule that the ASP.NET infrastructure realized was redundant. Specifically, I had the rule <i>DENY ALL USERS</i> in one folder, then attempted to add the same rule in a subfolder, which caused ASP.NET to throw an exception.<br /><br />Here's the error message that was thrown just now:<br /><br /><i>" + ex.Message + "</i></div>"
End Try
End Sub
Ahora creamos el procedimiento Create Rule
Protected Sub CreateRule(ByVal sender As Object, ByVal e As EventArgs)
Dim newRule As AuthorizationRule
If ActionAllow.Checked Then
newRule = New AuthorizationRule(AuthorizationRuleAction.Allow)
Else
newRule = New AuthorizationRule(AuthorizationRuleAction.Deny)
End If
If ApplyRole.Checked AndAlso UserRoles.SelectedIndex > 0 Then
newRule.Roles.Add(UserRoles.Text)
AddRule(newRule)
ElseIf ApplyUser.Checked AndAlso UserList.SelectedIndex > 0 Then
newRule.Users.Add(UserList.Text)
AddRule(newRule)
ElseIf ApplyAllUsers.Checked Then
newRule.Users.Add("*")
AddRule(newRule)
ElseIf ApplyAnonUser.Checked Then
newRule.Users.Add("?")
AddRule(newRule)
End If
End Sub
Nuestro siguiente metodo PullLocalRulesOutOfAuthorizationSection
Protected Function PullLocalRulesOutOfAuthorizationSection(ByVal section As AuthorizationSection) As ArrayList
' Dan Clem, 3/17/2007.
' First load the local rules into an ArrayList.
Dim rulesArray As New ArrayList()
For Each rule As AuthorizationRule In section.Rules
If rule.ElementInformation.IsPresent Then
rulesArray.Add(rule)
End If
Next
' Next delete the rules from the section.
For Each rule As AuthorizationRule In rulesArray
section.Rules.Remove(rule)
Next
Return rulesArray
End Function
Un poco mas de código
Protected Sub AddTheTwoSwappedRules(ByVal section As AuthorizationSection, ByVal rulesArray As ArrayList, ByVal selectedIndex As Integer, ByVal upOrDown As String)
If upOrDown = "up" Then
section.Rules.Add(DirectCast(rulesArray(selectedIndex), AuthorizationRule))
section.Rules.Add(DirectCast(rulesArray(selectedIndex - 1), AuthorizationRule))
ElseIf upOrDown = "down" Then
section.Rules.Add(DirectCast(rulesArray(selectedIndex + 1), AuthorizationRule))
section.Rules.Add(DirectCast(rulesArray(selectedIndex), AuthorizationRule))
End If
End Sub
Protected Sub AddFinalGroupOfRules(ByVal section As AuthorizationSection, ByVal rulesArray As ArrayList, ByVal selectedIndex As Integer, ByVal upOrDown As String)
Dim adj As Integer
If upOrDown = "up" Then
adj = 1
Else
adj = 2
End If
For x As Integer = selectedIndex + adj To rulesArray.Count - 1
section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
Next
End Sub
Protected Sub LoadRulesInNewOrder(ByVal section As AuthorizationSection, ByVal rulesArray As ArrayList, ByVal selectedIndex As Integer, ByVal upOrDown As String)
'
' * Imagine you have five local rules and you click a button to move the middle one.
' * In that scenario, all three of these methods will add rules.
' * If, however, there are only two local rules to start with, then only the middle method will add rules.
' * The first and third methods won't do anything, because their FOR loops will never execute.
'
AddFirstGroupOfRules(section, rulesArray, selectedIndex, upOrDown)
AddTheTwoSwappedRules(section, rulesArray, selectedIndex, upOrDown)
AddFinalGroupOfRules(section, rulesArray, selectedIndex, upOrDown)
End Sub
Protected Sub AddFirstGroupOfRules(ByVal section As AuthorizationSection, ByVal rulesArray As ArrayList, ByVal selectedIndex As Integer, ByVal upOrDown As String)
Dim adj As Integer
If upOrDown = "up" Then
adj = 1
Else
adj = 0
End If
For x As Integer = 0 To selectedIndex - adj - 1
section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
Next
End Sub
Protected Sub MoveRule(ByVal sender As Object, ByVal e As EventArgs, ByVal upOrDown As String)
'
upOrDown = upOrDown.ToLower()
If upOrDown = "up" OrElse upOrDown = "down" Then
Dim button As Button = DirectCast(sender, Button)
Dim item As GridViewRow = DirectCast(button.Parent.Parent, GridViewRow)
Dim selectedIndex As Integer = item.RowIndex
If (selectedIndex > 0 AndAlso upOrDown = "up") OrElse (upOrDown = "down") Then
Dim virtualFolderPath As String = FolderTree.SelectedValue
Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)
' Pull the local rules out of the authorization section, deleting them from same:
Dim rulesArray As ArrayList = PullLocalRulesOutOfAuthorizationSection(section)
If upOrDown = "up" Then
LoadRulesInNewOrder(section, rulesArray, selectedIndex, upOrDown)
ElseIf upOrDown = "down" Then
If selectedIndex < rulesArray.Count - 1 Then
LoadRulesInNewOrder(section, rulesArray, selectedIndex, upOrDown)
Else
' DOWN button in last row was pressed. Load the rules array back in without resorting.
For x As Integer = 0 To rulesArray.Count - 1
section.Rules.Add(DirectCast(rulesArray(x), AuthorizationRule))
Next
End If
End If
config.Save()
End If
End If
End Sub
Protected Sub DeleteRule(ByVal sender As Object, ByVal e As EventArgs)
'
'
' * This is working quite well, however there is a defect that I am not planning to fix right now.
' * If you delete a rule, then attempt to delete another rule from the same folder without
' * refreshing the page, you'll get a page error. The workaround is to re-click the folder in the
' * tree to refresh it, then delete the rule.
' * Don't feel like worrying about this right now.
' *
' * Note: this problem may have been fixed already.
' * I stopped using the session array method for handling things.
' * This may have fixed it. I'll test later.
'
Dim button As Button = DirectCast(sender, Button)
Dim item As GridViewRow = DirectCast(button.Parent.Parent, GridViewRow)
Dim virtualFolderPath As String = FolderTree.SelectedValue
Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
Dim section As AuthorizationSection = DirectCast(systemWeb.Sections("authorization"), AuthorizationSection)
section.Rules.RemoveAt(item.RowIndex)
config.Save()
End Sub
Protected Sub MoveUp(ByVal sender As Object, ByVal e As EventArgs)
MoveRule(sender, e, "up")
End Sub
Protected Sub MoveDown(ByVal sender As Object, ByVal e As EventArgs)
MoveRule(sender, e, "down")
End Sub
Function GetAction(ByVal rule As AuthorizationRule) As String
Return rule.Action.ToString()
End Function
Function GetRole(ByVal rule As AuthorizationRule) As String
Return rule.Roles.ToString()
End Function
Function GetUser(ByVal rule As AuthorizationRule) As String
Return rule.Users.ToString()
End Function
Protected Sub DisplayAccessRules(ByVal virtualFolderPath As String)
If Not virtualFolderPath.StartsWith(VirtualImageRoot) OrElse virtualFolderPath.IndexOf("..") >= 0 Then
'
' * from my brief testing, it appears that this may not be necessary, since ASP.NET seems to prevent this inherently, throwing this error:
' * Cannot use a leading .. to exit above the top directory.
'
'
Throw New ApplicationException("An attempt to access a folder outside of the website directory has been detected and blocked.")
End If
Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath)
Dim systemWeb As SystemWebSectionGroup = DirectCast(config.GetSectionGroup("system.web"), SystemWebSectionGroup)
Dim authorizationRules As AuthorizationRuleCollection = systemWeb.Authorization.Rules
RulesGrid.DataSource = authorizationRules
RulesGrid.DataBind()
TitleOne.InnerText = "Rules applied to " + virtualFolderPath
TitleTwo.InnerText = "Create new rule for " + virtualFolderPath
End Sub
Protected Sub RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs)
If e.Row.RowType = DataControlRowType.DataRow Then
Dim rule As AuthorizationRule = DirectCast(e.Row.DataItem, AuthorizationRule)
If Not rule.ElementInformation.IsPresent Then
e.Row.Cells(3).Text = "Inherited from higher level"
e.Row.Cells(4).Text = "Inherited from higher level"
e.Row.CssClass = "odd"
End If
End If
End Sub
Protected Sub ResetFolderImageUrls(ByVal parentNode As TreeNode)
'
'
' * I really wanted a strong visual queue indicating which folder was selected.
' * For some reason, the ImageUrl attribute of the <SelectedNodeStyle> tag does not work here,
' * so I resorted to this method after a number of other unsuccessful attempts.
' * Note that without this method, each successively selected folder will retain its target.gif
' * image on successive postbacks.
' *
' * A better way to do this would be to store the value path of the selected node in ViewState,
' * then use that value to find the previously selected node.
' * However, I could not figure out how to use the TreeView.FindNode(valuePath) method.
' * I even tried using hardcoded string values instead of the viewstate's stored valuePath string,
' * but nothing ever returned a valid node, whether I tried it in Page_Load or Page_PreRender.
'
parentNode.ImageUrl = "~/Styles/img/folder.gif"
' Recurse through this node's child nodes.
Dim nodes As TreeNodeCollection = parentNode.ChildNodes
For Each childNode As TreeNode In nodes
ResetFolderImageUrls(childNode)
Next
End Sub
Protected Sub FolderTree_SelectedNodeChanged(ByVal sender As Object, ByVal e As EventArgs)
'
'
' * I want to reset the Add Rule form field values whenever the user moves folders.
' * Note that the FALSE statements below are all necessary. It is not sufficient to set
' * the desired radio to TRUE, since the ASP.NET framework seems to treat radio buttons
' * as individual items even if they share the same group name.
'
ActionDeny.Checked = True
ActionAllow.Checked = False
ApplyRole.Checked = True
ApplyUser.Checked = False
ApplyAllUsers.Checked = False
ApplyAnonUser.Checked = False
UserRoles.SelectedIndex = 0
UserList.SelectedIndex = 0
RuleCreationError.Visible = False
ResetFolderImageUrls(FolderTree.Nodes(0))
' Restore previously selected folder's ImageUrl.
FolderTree.SelectedNode.ImageUrl = "~/Styles/img/target.gif"
' Set the newly selected folder's ImageUrl.
End Sub
Protected Function AddNodeAndDescendents(ByVal folder As DirectoryInfo, ByVal parentNode As TreeNode) As TreeNode
'
' * The PopulateTree and AddNodeAndDescendents are taken almost verbatim from Scott Mitchell's article
' * "Using the TreeView Control and a DataList to Create an Online Image Gallery", which is located at
' * http://aspnet.4guysfromrolla.com/articles/083006-1.aspx
'
' Add the TreeNode, displaying the folder's name and storing the full path to the folder as the value...
Dim virtualFolderPath As String
If parentNode Is Nothing Then
virtualFolderPath = VirtualImageRoot
Else
virtualFolderPath = parentNode.Value + folder.Name + "/"
End If
Dim node As New TreeNode(folder.Name, virtualFolderPath)
node.Selected = (folder.Name = selectedFolderName)
' Recurse through this folder's subfolders
Dim subFolders As DirectoryInfo() = folder.GetDirectories()
For Each subFolder As DirectoryInfo In subFolders
If subFolder.Name <> "_controls" AndAlso subFolder.Name <> "App_Data" Then
Dim child As TreeNode = AddNodeAndDescendents(subFolder, node)
node.ChildNodes.Add(child)
End If
Next
Return node
' Return the new TreeNode
End Function
Protected Sub PopulateTree()
'
'
' * The PopulateTree and AddNodeAndDescendents are taken almost verbatim from Scott Mitchell's article
' * "Using the TreeView Control and a DataList to Create an Online Image Gallery", which is located at
' * http://aspnet.4guysfromrolla.com/articles/083006-1.aspx
'
' Populate the tree based on the subfolders of the specified VirtualImageRoot
Dim rootFolder As New DirectoryInfo(Server.MapPath(VirtualImageRoot))
Dim root As TreeNode = AddNodeAndDescendents(rootFolder, Nothing)
FolderTree.Nodes.Add(root)
Try
FolderTree.SelectedNode.ImageUrl = "/Simple/i/target.gif"
Catch
End Try
End Sub
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
UserRoles.DataSource = Roles.GetAllRoles()
UserRoles.DataBind()
UserList.DataSource = Membership.GetAllUsers()
UserList.DataBind()
If IsPostBack Then
selectedFolderName = ""
Else
selectedFolderName = Request.QueryString("selectedFolderName")
End If
crabit.RegCss.gridcss(Me)
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
PopulateTree()
End If
End Sub
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
' * The call to DisplayAccessRules, and the subsequent binding of the gridview, MUST occur
' * inside the Page_PreRender event. I don't fully understand why, but this fixes two more .NET gotchas:
' * 1) My technique to hide the delete rule button did not work the other way.
' * Why? Upon page refresh, ALL ROWS HAD BUTTONS because I'd been calling DisplayAccessRules
' * from the FolderTree_SelectedNodeChanged event.
' * 2) I first moved this into the Page_Load event, but that caused the weird Event validation error
' * that occurs when ASP.NET thinks someone is hacking your postback values.
' *
' * This appears to be working now, so I'm leaving well enough alone.
'
If FolderTree.SelectedNode IsNot Nothing Then
DisplayAccessRules(FolderTree.SelectedValue)
SecurityInfoSection.Visible = True
End If
End Sub