Would showing primary key to the public be potential security risk? - security

Well the question is self-explanatory. One more thing would be, if it is a security risk then please provide an example of what the user could do. In this case the primary key would be something like : "Product ID"
Thanks!

No more than showing any other piece of data. If you're vulnerable to SQL injection attacks then showing the primary key is probably the least of your concerns.
Think about it this way, if someone can execute arbitrary sql against your db, which is going to cause more harm: delete from users where id = 100 or delete from users where surname = 'smith'?

I don't think there is an inherent risk of exposing the primary key field, but I see no advantage to advertising it as such. (I know I may be reading deeper into your question than you intended)
If you have a ProductId that lets your client identify some product, that's fine to display. The security risk is minimal, IMO. I just would not expose the fact that you used ProductId as a primary key - implementation details should be hidden, even while exposing the content.

Not necessarily. But if you ever wind up changing primary key types along the way (or database providers altogether necessitating the change), abstracting your primary keys to your application-level developers is a good idea. What you do is create an interface which abstracts the primary key at that level, but the data layer knows about it via its implementation. Here's an example:
namespace Aesop.DataAccess
{
// System namespaces
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Xml.Serialization;
/// <summary>
/// Represents an object's unique key in order to abstract out the
/// underlying key generation/maintenance mechanism.
/// </summary>
/// <typeparam name="T">The type the key is representing.</typeparam>
[ServiceContract]
public interface IModelIdentifier<T> : ISerializable, IXmlSerializable
{
/// <summary>
/// Gets a string representation of the domain the model originated
/// from.
/// </summary>
string Origin
{
[OperationContract]
get;
}
/// <summary>
/// The model instance identifier for the model object that this
/// <see cref="IModelIdentifier{T}"/> refers to. Typically, this
/// is a database key, file name, or some other unique identifier.
/// </summary>
/// <typeparam name="TKeyDataType">The expected data type of the
/// identifier.</typeparam>
/// <returns>The unique key as the data type specified.</returns>
[OperationContract]
TKeyDataType GetKey<TKeyDataType>();
/// <summary>
/// Performs an equality check on the two model identifiers and returns
/// <c>true</c> if they are equal; otherwise <c>false</c> is returned.
/// All implementations must also override the equal operator.
/// </summary>
/// <param name="obj">The identifier to compare against.</param>
/// <returns>
/// <c>true</c> if the identifiers are equal; otherwise
/// <c>false</c> is returned.
/// </returns>
[OperationContract]
bool Equals(IModelIdentifier<T> obj);
}
}
And here's an implementation for a "standard" int primary key:
namespace Aesop.DataAccess
{
// System namespaces
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
/// <summary>
/// Represents an abstraction of the database key for a Model Identifier.
/// </summary>
/// <typeparam name="T">The expected owner data type for this identifier.
/// </typeparam>
[DebuggerDisplay("Integer Identifier={id}, Origin={Origin}")]
[Serializable]
public sealed class IntegerIdentifier<T> : IModelIdentifier<T> where T : class, ISerializable, IXmlSerializable
{
/// <summary>
/// The unique ID.
/// </summary>
private int id;
/// <summary>
/// Initializes a new instance of the <see cref="IntegerIdentifier<T>"/> class.
/// </summary>
/// <param name="id">The unique ID.</param>
public IntegerIdentifier(int id)
{
this.id = id;
}
/// <summary>
/// Initializes a new instance of the <see cref="IntegerIdentifier<T>"/> class.
/// </summary>
/// <param name="info">The
/// <see cref="T:System.Runtime.Serialization.SerializationInfo"/> from
/// which to retrieve the data.</param>
/// <param name="context">The source (see
/// <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for
/// this deserialization.</param>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
private IntegerIdentifier(
SerializationInfo info,
StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
this.id = info.GetInt32("id");
}
/// <summary>
/// Prevents a default instance of the <see cref="IntegerIdentifier<T>"/> class from being created.
/// </summary>
private IntegerIdentifier()
{
}
/// <summary>
/// Gets a string representation of the domain the model originated
/// from.
/// </summary>
public string Origin
{
get
{
return this.GetType().Namespace;
}
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="integerIdentifier1">The first Model Identifier to
/// compare.</param>
/// <param name="integerIdentifier2">The second Model Identifier to
/// compare.</param>
/// <returns>
/// <c>true</c> if the instances are equal; otherwise
/// <c>false</c> is returned.
/// </returns>
public static bool operator ==(
IntegerIdentifier<T> integerIdentifier1,
IntegerIdentifier<T> integerIdentifier2)
{
return object.Equals(integerIdentifier1, integerIdentifier2);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="integerIdentifier1">The first Model Identifier to
/// compare.</param>
/// <param name="integerIdentifier2">The second Model Identifier to
/// compare.</param>
/// <returns>
/// <c>true</c> if the instances are equal; otherwise
/// <c>false</c> is returned.
/// </returns>
public static bool operator !=(
IntegerIdentifier<T> integerIdentifier1,
IntegerIdentifier<T> integerIdentifier2)
{
return !object.Equals(integerIdentifier1, integerIdentifier2);
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is
/// equal to the current <see cref="T:System.Object"/>.
/// </summary>
/// <param name="obj">The <see cref="T:System.Object"/> to compare with
/// the current <see cref="T:System.Object"/>.</param>
/// <returns>true if the specified <see cref="T:System.Object"/> is
/// equal to the current <see cref="T:System.Object"/>; otherwise,
/// false.</returns>
/// <exception cref="T:System.NullReferenceException">The
/// <paramref name="obj"/> parameter is null.</exception>
public override bool Equals(object obj)
{
return this.Equals(obj as IModelIdentifier<T>);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="T:System.Object"/>.
/// </returns>
public override int GetHashCode()
{
return this.id.GetHashCode();
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String"/> that represents this instance.
/// </returns>
public override string ToString()
{
return this.id.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// The model instance identifier for the model object that this
/// <see cref="IModelIdentifier{T}"/> refers to. Typically, this is a
/// database key, file name, or some other unique identifier.
/// </summary>
/// <typeparam name="TKeyDataType">The expected data type of the
/// identifier.</typeparam>
/// <returns>The unique key as the data type specified</returns>
public TKeyDataType GetKey<TKeyDataType>()
{
return (TKeyDataType)Convert.ChangeType(
this.id,
typeof(TKeyDataType),
CultureInfo.InvariantCulture);
}
/// <summary>
/// Performs an equality check on the two model identifiers and
/// returns <c>true</c> if they are equal; otherwise <c>false</c>
/// is returned. All implementations must also override the equal
/// operator.
/// </summary>
/// <param name="obj">The identifier to compare against.</param>
/// <returns>
/// <c>true</c> if the identifiers are equal; otherwise
/// <c>false</c> is returned.
/// </returns>
public bool Equals(IModelIdentifier<T> obj)
{
if (obj == null)
{
return false;
}
return obj.GetKey<int>() == this.GetKey<int>();
}
/// <summary>
/// Populates a
/// <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with
/// the data needed to serialize the target object.
/// </summary>
/// <param name="info">The
/// <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to
/// populate with data.</param>
/// <param name="context">The destination (see
/// <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for
/// this serialization.</param>
/// <exception cref="T:System.Security.SecurityException">The caller
/// does not have the required permission. </exception>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(
SerializationInfo info,
StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("id", this.id);
}
/// <summary>
/// This method is reserved and should not be used. When implementing
/// the IXmlSerializable interface, you should return null (Nothing in
/// Visual Basic) from this method, and instead, if specifying a custom
/// schema is required, apply the
/// <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/>
/// to the class.
/// </summary>
/// <returns>
/// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the
/// XML representation of the object that is produced by the
/// <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/>
/// method and consumed by the
/// <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>
/// method.
/// </returns>
public XmlSchema GetSchema()
{
return null;
}
/// <summary>
/// Generates an object from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/>
/// stream from which the object is deserialized.</param>
public void ReadXml(XmlReader reader)
{
if (reader != null)
{
this.id = Convert.ToInt32(
reader.GetAttribute("id"),
CultureInfo.InvariantCulture);
}
}
/// <summary>
/// Converts an object into its XML representation.
/// </summary>
/// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/>
/// stream to which the object is serialized.</param>
public void WriteXml(XmlWriter writer)
{
if (writer != null)
{
writer.WriteAttributeString(
"id",
this.id.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Generates an object from its string representation.
/// </summary>
/// <param name="value">The value of the model's type.</param>
/// <returns>A new instance of this class as it's interface containing
/// the value from the string.</returns>
internal static IModelIdentifier<T> FromString(string value)
{
int id;
if (int.TryParse(
value,
NumberStyles.None,
CultureInfo.InvariantCulture,
out id))
{
return new IntegerIdentifier<T>(id);
}
return null;
}
/// <summary>
/// Generates an object from its XML representation.
/// </summary>
/// <param name="reader">The <see cref="T:System.Xml.XmlReader"/>
/// stream from which the object is deserialized.</param>
/// <returns>A new instance of this class as it's interface containing
/// the value from the XmlReader.</returns>
internal static IModelIdentifier<T> FromXml(XmlReader reader)
{
if (reader != null)
{
return new IntegerIdentifier<T>(Convert.ToInt32(
reader.GetAttribute("id"),
CultureInfo.InvariantCulture));
}
return null;
}
}
}

if it includes readable information for some confidential data element, then yes.

Obviously that depends on what information the key contains. It's conceivable that it could be sensitive (an account number perhaps). Maybe Product ID is senstitive information in your application but I don't see why the fact that it's a primary key should make it any more or less of a security risk.

Related

Xamarin iOS- NFC Reader- INFCNdefReaderSessionDelegate- DidDetect not getting called

I have a Cross-Platform Xamarin app iOS and wanted to add the nfc-scan functionality.
I followed the link- https://learn.microsoft.com/en-us/xamarin/ios/platform/introduction-to-ios11/corenfc
These have been configured properly-
Info.plist privacy key.
Entitlements.plist entry.
provisioning profile with NFC Tag Reading capability.
I've put all into a new RFIDReader class which is present in a iOS Class Library, and this method gets called from my ViewModel class.
If I run this code, everything looks fine. The scan starts when I press the button on my phone, it shows the blue tick symbol but then it never gets into one of the implemented methods DidDetect, DidInvalidate.
Do you know what the reason could be?
Here's my code-
/// <summary>
/// Class for RFID form control
/// </summary>
public sealed class RFIDReader : UIViewController, IRFIDReader, INFCNdefReaderSessionDelegate
{
/// <summary>
/// The scanning session
/// </summary>
private NFCNdefReaderSession session;
/// <summary>
/// The task completion source
/// </summary>
private TaskCompletionSource<string> tcs;
/// <summary>
/// Gets the handle
/// </summary>
/// <value>The handle</value>
public new IntPtr Handle { get; }
/// <summary>
/// Read the string characters from scanning
/// </summary>
/// <returns>Task status</returns>
public Task<string> ScanCode()
{
if (!NFCNdefReaderSession.ReadingAvailable)
{
//// NFC is not available or disabled
throw new Exception(FormsErrorConstants.NearFieldCommunicationDisabled);
}
this.tcs = new TaskCompletionSource<string>();
this.session = new NFCNdefReaderSession(this, DispatchQueue.CurrentQueue, true);
this.session.BeginSession();
return this.tcs.Task;
}
/// <summary>
/// Called when a tag is successfully read
/// </summary>
/// <param name="session">Session</param>
/// <param name="messages">Messages</param>
public void DidDetect(NFCNdefReaderSession session, NFCNdefMessage[] messages)
{
var bytes = messages[0].Records[0].Payload.Skip(3).ToArray();
var message = Encoding.UTF8.GetString(bytes);
this.tcs.SetResult(message);
}
/// <summary>
/// Called when an error occurs or the 60 second timeout is reached
/// </summary>
/// <param name="session">Session</param>
/// <param name="error">Error</param>
public void DidInvalidate(NFCNdefReaderSession session, NSError error)
{
var readerError = (NFCReaderError)(long)error.Code;
if (readerError != NFCReaderError.ReaderSessionInvalidationErrorFirstNDEFTagRead &&
readerError != NFCReaderError.ReaderSessionInvalidationErrorUserCanceled)
{
// some error handling
}
this.tcs.SetResult(string.Empty);
}
/// <summary>
/// Clean up method
/// </summary>
public new void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Clean up method
/// </summary>
/// <param name="isDisposing">is disposing</param>
private new void Dispose(bool isDisposing)
{
if (isDisposing)
{
// Dispose
this.session.InvalidateSession();
this.session = null;
}
}
}

How can I truncate ${level} in nlog layout to a single character?

In the log I would like:
...D...
instead of
...Debug...
Same of course for Info, Warn, etc. I have tried ${level[0]} and other variations for the layout, no luck.
Update
Since NLog 4.4.6 you could also do ${level:format=FirstCharacter}. See docs
Original answer
You could do it with a ${when} which is a bit cumbersome:
e.g.
${when:when=level=LogLevel.Trace:inner=T:else=${when:when=level=LogLevel.Debug:inner=D:else=TODO}}
Or you could add a "wrapper" with some code:
/// <summary>
/// Take the left characters
/// </summary>
/// <example>
/// ${left:${level}:Length=2} //[DefaultParameter]
/// ${left:Inner=${level}:Length=2}
/// ${level:Length=2} //[ambient]
/// </example>
[LayoutRenderer("left")]
[AmbientProperty("Left")]
[ThreadAgnostic]
public sealed class LeftLayoutRendererWrapper : WrapperLayoutRendererBase
{
/// <summary>
/// Gets or sets the length in characters.
/// </summary>
/// <value>Index</value>
/// <docgen category='Transformation Options' order='10' />
[DefaultValue(1)]
public int Length { get; set; }
/// <summary>
/// Post-processes the rendered message.
/// </summary>
/// <param name="text">The text to be post-processed.</param>
/// <returns>Substringed</returns>
protected override string Transform(string text)
{
if (text == null)
{
return null;
}
if (Length <= 0)
{
return String.Empty;
}
return text.Substring(0, Length);
}
}
register it (as soon as possible, eg. in main() or app_start())
ConfigurationItemFactory.Default.LayoutRenderers
.RegisterDefinition("left", typeof(MyNamespace.LeftLayoutRendererWrapper ));
usage:
${left:${level}:Length=2} //or
${level:Length=2} //"ambient way"

MVC 5- changing code infrastructure from old Forms authentication to OWIN forms authentication

I'm trying to migrate an old .NET 3.5, MVC 1 project to a new .NET 4.5 MVC 5 project. I have run into a road block where there is old security/authentication infrastructure in place and I am not quite sure on how to change it over to OWIN claims based authentication. I am still trying to maintain FormsAuthentication with OWIN. A couple of classes:
DomainPrincipal.cs
public sealed class DomainPrincipal : MarshalByRefObject, IPrincipal
{
private readonly IIdentity _identity;
private readonly User _user;
/// <summary>
/// Initializes a new instance of the <see cref="DomainPrincipal"/> class.
/// </summary>
/// <param name="identity">The identity.</param>
/// <param name="user">The user.</param>
public DomainPrincipal(IIdentity identity, User user)
{
_identity = identity;
_user = user;
}
/// <summary>
/// Gets the user.
/// </summary>
/// <value>The <see cref="User"/> associated with the current principal.</value>
public User User
{
get { return _user; }
}
#region IPrincipal Members
/// <summary>
/// Determines whether the current principal belongs to the specified role.
/// </summary>
/// <returns>
/// <c>true</c> if the current principal is a member of the specified role; otherwise <c>false</c>.
/// </returns>
/// <param name="roleName">The name of the role for which to check membership. </param>
public Boolean IsInRole(String roleName)
{
return User.HasPrivilege(roleName);
}
/// <summary>
/// Gets the identity of the current principal.
/// </summary>
/// <value></value>
/// <returns>The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal.</returns>
public IIdentity Identity
{
get { return _identity; }
}
#endregion
}
WebFormsAuthenticatedContext.cs - uses DomainPrincipal class to implement AuthenticatedContext for a forms authentication environment.
public sealed class WebFormsAuthenticatedContext : AuthenticatedContext
{
/// <summary>
/// Gets the current user.
/// </summary>
/// <value>The current user.</value>
/// <remarks>Determines the current user from the user principal of the current <see cref="HttpContext"/>.</remarks>
public User User
{
get { return IsValid ? ((DomainPrincipal)HttpContext.Current.User).User : null; }
}
/// <summary>
/// Gets a value indicating whether the <see cref="User"/> property is currently valid.
/// </summary>
/// <value>
/// <c>true</c> if the <see cref="User"/> property is valid; otherwise, <c>false</c>.
/// </value>
public Boolean IsValid
{
get { return HttpContext.Current != null && HttpContext.Current.User is DomainPrincipal; }
}
}
ServiceAuthenticatedContext.cs - Implements AuthenticatedContext for a WCF service environment.
public class ServiceAuthenticatedContext : AuthenticatedContext
{
/// <summary>
/// Gets or sets the user DAO.
/// </summary>
/// <value>The user DAO.</value>
public UserDao UserDao { get; set; }
#region AuthenticatedContext Members
/// <summary>
/// Gets the current user.
/// </summary>
/// <value>The current user.</value>
/// <remarks>Determines the user identity from the current <see cref="ServiceSecurityContext"/>.</remarks>
public User User
{
get
{
const string AuthorizationContextKey = "DomainPrincipal";
AuthorizationContext authorizationContext = ServiceSecurityContext.Current.AuthorizationContext;
User user;
if (authorizationContext.Properties.ContainsKey(AuthorizationContextKey))
{
user = authorizationContext.Properties[AuthorizationContextKey] as User;
}
else
{
string username = ServiceSecurityContext.Current.PrimaryIdentity.Name;
user = UserDao.GetByUsername(username);
authorizationContext.Properties.Add(AuthorizationContextKey, user);
}
return user;
}
}
public String Username { get; private set; }
/// <summary>
/// Gets a value indicating whether the <see cref="User"/> property is currently valid.
/// </summary>
/// <value>
/// <c>true</c> if the <see cref="User"/> property is valid; otherwise, <c>false</c>.
/// </value>
public bool IsValid
{
get { return ServiceSecurityContext.Current.PrimaryIdentity != null && ServiceSecurityContext.Current.PrimaryIdentity.IsAuthenticated; }
}
#endregion
}
I am now looking at achieving something similar using OWIN, signing the user in like so:
// Log user in
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, model.Username),
},
DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
Authentication.SignIn(new AuthenticationProperties
{
IsPersistent = model.RememberMe
}, identity);
and using the custom DomainPrincipal class in place as a ClaimsPrincipal instead (changing it accordingly) and changing WebFormsAuthenticatedContext and ServiceAuthenticatedContext classes to work with this new DomainPrincipal and OWIN security model.
Is this possible to do? Or would I need to revamp the whole authentication infrastructure completely?
Your help and advice would be much appreciated.
Thank you.
Have a look at this blog
It may give you more insight into how to implement Owin authentication.
To answer your question, if all you want to use a custom principal for handling roles - DomainPrinicpal(extending the IPrincipal), you just need to replace the HttpContext.Current.User as Request.GetOwinContext().Request.User in the WebFormsAuthenticatedContext class.

Custom Data Type: Rendering the name of the type and not the value of the property

There is a custom data type for business object properties in one of my projects. This custom type is a wrapper for the basic Data Types in .NET.
When I try and get the value from the property, the below is displayed if the syntax is:
company.Name
Interfaces.Model.CustomType`1[System.String]
It is expecting:
company.Name.Value
I would like to avoid the need to use the .value; am I looking to overload an operation, or implicit/explicit methods?
Any help would be great.
Here is the general outline of the CustomType:
public class CustomType<t>
{
#region Implicit Casting
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static implicit operator t(CustomType<t> obj)
{
if (obj == null)
return new CustomType<t>().Value;
return obj.Value;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static implicit operator CustomType<t>(t obj)
{
return new CustomType<t>(obj);
}
#endregion Implicit Casting
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>The value.</value>
public t Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
/// <summary>
/// Sets the value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public CustomType<t> setValue(t value)
{
try
{
Value = value;
}
catch (InvalidCastException e)
{
throw new InvalidCastException("CustomType invalid property cast ", e.InnerException);
}
return this;
}
}
If I understand correctly you need to override ToString.
public class CustomType<T>
{
public override string ToString()
{
return Value.ToString(); //I assume Value is of type T.
}
}
I've done a certain amount of guessing here, perhaps you could show the code for your custom type and the all that's giving you the type.

Best Pattern for AllowUnsafeUpdates

So far, in my research I have seen that it is unwise to set AllowUnsafeUpdates on GET request operation to avoid cross site scripting. But, if it is required to allow this, what is the proper way to handle the situation to mitigate any exposure?
Here is my best first guess on a reliable pattern if you absolutely need to allow web or site updates on a GET request.
Best Practice?
protected override void OnLoad(System.EventArgs e)
{
if(Request.HttpMethod == "POST")
{
SPUtility.ValidateFormDigest();
// will automatically set AllowSafeUpdates to true
}
// If not a POST then AllowUnsafeUpdates should be used only
// at the point of update and reset immediately after finished
// NOTE: Is this true? How is cross-site scripting used on GET
// and what mitigates the vulnerability?
}
// Point of item update
using(SPSite site = new SPSite(SPContext.Current.Site.Url, SPContext.Current.Site.SystemAccount.UserToken))
{
using (SPWeb web = site.RootWeb)
{
bool allowUpdates = web.AllowUnsafeUpdates; //store original value
web.AllowUnsafeUpdates = true;
//... Do something and call Update() ...
web.AllowUnsafeUpdates = allowUpdates; //restore original value
}
}
Feedback on the best pattern is appreciated.
If you're performing any operations which modify something, then anyone that can convince the user to click on a link can perform that operation. For instance, let's assume that you have a GET request to a page which lets the user add an administrator to a site, and the user clicks a link to a page which does a Response.Redirect("http://yourserver/_layouts/admin.aspx?operation=addAdministrator&username=attackerNameHere").
While normally a POST does not offer much protection against this (nothing will stop someone from having a <form method="post" action="http://yourserver/_layouts/admin.aspx">), SharePoint has a concept of form digests, which contain information about the previous request that is generating the post back (including the user's name). This reduces the footprint for this kind of attack significantly.
The only time that it is not a security issue to AllowUnsafeUpdates on a GET is if you're not taking input from the user. For instance, if you have a web part which also logs visits to a list, then there's no security vulnerability exposed.
Edit: If you are going to use AllowUnsafeUpdates, there's no need to reset it to its previous value. It does not get persisted. It's just something you need to set on an SPWeb object before performing updates from a GET (or other cases)
I would slightly modify Trent's delegate to accept the web to update:
public static void DoUnsafeUpdate(this SPWeb web, Action<SPWeb> action)
{
try
{
web.AllowUnsafeUpdates = true;
action(web);
}
finally
{
web.AllowUnsafeUpdates = false;
}
}
And then extend HttpContext to encapsulate verification of the form digest, with an option to elevate using the technique described here:
public static void DoUnsafeUpdate(this HttpContext context, Action<SPWeb> action, bool elevated)
{
SPWeb web = SPControl.GetContextWeb(context);
if (!context.Request.HttpMethod.Equals("POST", StringComparison.Ordinal)
|| web.ValidateFormDigest())
throw new SPException("Error validating postback digest");
if (elevated)
web.RunAsSystem(w => w.DoUnsafeUpdate(action));
else
web.DoUnsafeUpdate(action);
}
Usage:
protected override void OnLoad(System.EventArgs e)
{
Context.DoUnsafeUpdate(web =>
{
// Update elevated web
}, true);
}
Another clean way to implement would be to use a combination of extension methods and anonymous delegates as such:
public static void DoUnsafeUpdate(this SPWeb web, Action action)
{
bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
web.AllowUnsafeUpdates = true;
action();
web.AllowUnsafeUpdates = allowUnsafeUpdates;
}
Using the above extension method, you can then perform your "unsafe update" action as follows:
var web = SPContext.Current.Web;
web.DoUnsafeUpdate(delegate()
{
// Put your "unsafe update" code here
});
For AllowUnsafeUpdates, I follow this process:
if( HttpContext.Current is null )
{
Do nothing, no need to set AllowUnsafeUpdates to true nor
to call ValidateFormDigest() because update will be carried out
}
else // HttpContext.Current is NOT null
{
if( SPContext.Current is null )
{
Need to set AllowUnsafeUpdates to true
}
else // SPContext.Current is NOT null
{
Call ValidateFormDigest()
}
}
Not so sure it is worth remembering the previous value of allow unsafe updates.
I would want to wrap the call around the minimum possible amount of code, so that nested calls to it would not occur.
Then you can just turn it to false afterwards.
I use a wrapper class for handling most manipulation of SPWeb objects. This helps me remember to close the web, and it eases the problems of unsafeupdates setting. It is a bit bloated, as I have patched on new constructors and members. but, then again; so is the SPWeb class.
Usage:
using (WebWrapper wrapper = new WebWrapper("http://localhost"))
{
wrapper.AllowUnsafeUpdates();
//Do work on wrapper.
}
The class definition:
using System;
using System.Collections.Specialized;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace Skaar.SharePoint.Customization
{
/// <summary>
/// A wrapper for a <see cref="SPWeb"/> object.
/// <remarks>Closes web object on Dispose if applicable.</remarks>
/// </summary>
[Serializable]
[DebuggerDisplay("{Uri} Unsafe:{AllowUnsafeUpdatesSetting} Update:{UpdatePending}")]
public sealed class WebWrapper : IDisposable, IDeserializationCallback, IEquatable<WebWrapper>
{
[NonSerialized] private bool unsafeUpdatesSetting;
[NonSerialized] private SPWeb web;
/// <summary>
/// Determines if the inner web object should be closed.
/// </summary>
[NonSerialized] private bool webShouldBeClosed;
/// <summary>
/// This value is used in serialization to restore <see cref="Web"/>.
/// </summary>
private string webUrl;
/// <summary>
/// Creates a new wrapper object.
/// </summary>
/// <param name="web">A web that should be closed/disposed when done.</param>
public WebWrapper(SPWeb web) : this(web, true)
{
}
/// <summary>
/// Creates a new wrapper object.
/// </summary>
/// <param name="web">An inner web object</param>
/// <param name="webShouldBeClosed">If true, the web object is closed in the <see cref="Dispose()"/> method.</param>
public WebWrapper(SPWeb web, bool webShouldBeClosed)
{
setWeb(web, webShouldBeClosed);
}
/// <summary>
/// Creates a new wrapper object.
/// </summary>
/// <param name="webAddress">The address to a web.</param>
public WebWrapper(Uri webAddress)
{
using (SPSite site = new SPSite(webAddress.ToString()))
{
string relativeUrl = renderWebRootRelativeUrl(webAddress);
if (relativeUrl == null)
{
setWeb(site.OpenWeb(), true);
}
else
{
setWeb(site.OpenWeb(relativeUrl), true);
}
}
}
private string renderWebRootRelativeUrl(Uri address)
{
for (int i = 0; i < address.Segments.Length; i++)
{
string segment = address.Segments[i];
if (string.Equals(segment, "_layouts/"))
{
string newUrl=string.Join(null, address.Segments, 0, i).Trim('/');
return newUrl;
}
}
return null;
}
/// <summary>
/// If true, <see cref="SPWeb.Update"/> will be called in <see cref="Dispose()"/>.
/// </summary>
public bool UpdatePending { get; private set; }
/// <summary>
/// The setting of the inner web (<see cref="SPWeb.AllowUnsafeUpdates"/>)
/// </summary>
public bool AllowUnsafeUpdatesSetting
{
get { return Web.AllowUnsafeUpdates; }
}
/// <summary>
/// The inner object.
/// </summary>
/// <exception cref="ObjectDisposedException">Exception is thrown if <see cref="IsDisposed"/> is true.</exception>
public SPWeb Web
{
get
{
if(IsDisposed)
{
throw new ObjectDisposedException("Web wrapper is disposed.");
}
return web;
}
}
/// <summary>
/// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
/// </summary>
public Uri Uri
{
get { return new Uri(Web.Url); }
}
/// <summary>
/// The address of the <see cref="Web"/> wrapped as a <see cref="Uri"/> object.
/// </summary>
public Uri GetUri(SPUrlZone zone)
{
return Site.WebApplication.GetResponseUri(zone, Uri.AbsolutePath);
}
/// <summary>
/// Creates a wrapper around the context web.
/// <remarks>The web will not be closed when wrapper is disposed. Returns null if context is unavailable.</remarks>
/// </summary>
public static WebWrapper Context
{
get
{
return SPContext.Current==null?null:new WebWrapper(SPContext.Current.Web, false);
}
}
/// <summary>
/// This is a static property wrapping of
/// the <see cref="CloneOf(SPWeb)"/> method, using
/// the <see cref="SPContext"/> current web as
/// parameter.
/// <remarks>Returns null if context is unavailable.</remarks>
/// </summary>
public static WebWrapper CloneOfContext
{
get
{
if (SPContext.Current != null)
{
SPWeb contextWeb = SPContext.Current.Web;
return CloneOf(contextWeb);
}
return null;
}
}
/// <summary>
/// Returns the <see cref="SPWeb.Exists"/> property of the <see cref="Web"/> object.
/// </summary>
public bool Exists
{
get { return Web != null && Web.Exists; }
}
/// <summary>
/// Gets the <see cref="SPSite"/> object of <see cref="Web"/>.
/// </summary>
/// <remarks>This object should not be closed by user code.</remarks>
public SPSite Site
{
get { return web.Site; }
}
/// <summary>
/// Gets the owner defined in <see cref="SPSite.Owner"/>.
/// </summary>
public SPUser Owner
{
get
{
return Site.Owner;
}
}
/// <summary>
/// Returns a context of the inner <see cref="Web"/>.
/// </summary>
public SPContext ContextOfWeb
{
get { return SPContext.GetContext(web); }
}
/// <summary>
/// Gets the language of <see cref="Web"/>.
/// </summary>
public CultureInfo Locale
{
get { return Web.Locale; }
}
/// <summary>
/// Gets the language of the root web.
/// </summary>
public CultureInfo LocaleOfRoot
{
get
{
using (WebWrapper root = Root)
{
return root.Locale;
}
}
}
/// <summary>
/// Returns a new <see cref="WebWrapper"/> wrapping the root <see cref="SPWeb"/> of this.
/// </summary>
public WebWrapper Root
{
get
{
if (webShouldBeClosed)
using (SPSite site = Site)
{
return new WebWrapper(site.RootWeb);
}
return new WebWrapper(Site.RootWeb);
}
}
/// <summary>
/// A wrapper for <see cref="SPWeb.Title"/>.
/// </summary>
public string Title
{
get { return Web.Title; }
set { Web.Title = value; }
}
/// <summary>
/// A wrapper for <see cref="SPWeb.ID"/>.
/// </summary>
public Guid ID
{
get { return Web.ID; }
}
#region Web Properties
[NonSerialized] private bool updatePropertiesPending;
/// <summary>
/// A wrapper method to <see cref="Web"/> object's <see cref="SPWeb.Properties"/> indexer.
/// </summary>
/// <param name="key">The key to use when fetching property value.</param>
/// <returns>A string containing the value.</returns>
public string GetProperty(string key)
{
return Web.Properties[key];
}
/// <summary>
/// Sets the value in the <see cref="Web"/> object's <see cref="SPWeb.Properties"/>. Creates a new key, or updates an existing as needed.
/// </summary>
/// <param name="key">The key to use when storing the property value.</param>
/// <param name="value">The value to set in the key.</param>
/// <remarks>The property <see cref="UpdatePending"/> is set to true.</remarks>
public void SetProperty(string key, string value)
{
if (!Web.Properties.ContainsKey(key))
{
Web.Properties.Add(key, value);
}
else
{
Web.Properties[key] = value;
}
updatePropertiesPending = true;
}
#endregion
#region IDeserializationCallback Members
///<summary>
///Runs when the entire object graph has been deserialized.
///</summary>
///
///<param name="sender">The object that initiated the callback. The functionality for this parameter is not currently implemented. </param>
public void OnDeserialization(object sender)
{
using (SPSite site = new SPSite(webUrl))
{
setWeb(site.OpenWeb(), true);
}
}
#endregion
#region IDisposable Members
///<summary>
///Closes inner web object if appropriate.
///</summary>
///<filterpriority>2</filterpriority>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool isDisposing)
{
if (IsDisposed) return;
if (isDisposing)
{
doDisposeOfWeb();
IsDisposed = true;
}
}
#endregion
/// <summary>
/// Value is true if <see cref="Dispose()"/> method has been called. Object is not in a usable state.
/// </summary>
internal bool IsDisposed
{
get; private set;
}
#region IEquatable<WebWrapper> Members
/// <summary>
/// This tests whether the two objects wraps the same web. It may however be two different instances of the same web.
/// </summary>
/// <param name="other">Another wrapper object.</param>
/// <returns>True if <see cref="Uri"/> equals, false otherwise.</returns>
public bool Equals(WebWrapper other)
{
if (other == null)
{
return false;
}
return Uri.Equals(other.Uri);
}
#endregion
/// <summary>
/// Reopens the inner <see cref="SPWeb"/> object. May be used when web object needs to be rereferenced in a new security context.
/// </summary>
public void ReOpen()
{
bool unsafeSetting = AllowUnsafeUpdatesSetting;
using (SPSite site = new SPSite(Web.Url))
{
SPWeb newWeb = site.OpenWeb();
doDisposeOfWeb();
web = newWeb;
web.AllowUnsafeUpdates = unsafeSetting;
unsafeUpdatesSetting = false;
webShouldBeClosed = true;
}
}
private void doDisposeOfWeb()
{
if (Web == null) return;
Update(true);
if (webShouldBeClosed)
{
Web.Close();
}
else if (Web.Exists)
{
Web.AllowUnsafeUpdates = unsafeUpdatesSetting;
}
web = null;
}
/// <summary>
/// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
/// </summary>
public void Update()
{
Update(false);
}
/// <summary>
/// Sets <see cref="UpdatePending"/> to <c>true</c>.
/// </summary>
public void SetUpdatePending()
{
UpdatePending = true;
}
/// <summary>
/// Calls <see cref="SPWeb.Update"/> on the <see cref="Web"/> object.
/// <param name="onlyIfPending">If true, update will depend on state of the <see cref="UpdatePending"/> property.</param>
/// </summary>
public void Update(bool onlyIfPending)
{
if (onlyIfPending)
{
if (updatePropertiesPending)
{
Web.Properties.Update();
updatePropertiesPending = false;
}
if (UpdatePending)
{
Web.Update();
UpdatePending = false;
}
}
else
{
Web.Update();
UpdatePending = false;
}
}
/// <summary>
/// Returns the list from <see cref="Web"/> with <see cref="SPList.Title"/> equal to <see cref="title"/>.
/// </summary>
/// <param name="title">The <see cref="SPList.Title"/> of an existing list.</param>
/// <returns>The first list found with the given title, or null, if no list is found.</returns>
public SPList GetList(string title)
{
foreach (SPList list in Web.Lists)
{
if (list.Title == title)
{
return list;
}
}
return null;
}
/// <summary>
/// A wrapper method to the <see cref="Web"/> object's <see cref="SPWeb.Lists"/> indexer.
/// </summary>
/// <param name="id">The id of the list to return.</param>
/// <returns>The list with the supplied id.</returns>
public SPList GetList(Guid id)
{
return Web.Lists[id];
}
private void setWeb(SPWeb innerWeb, bool shouldBeClosed)
{
if (innerWeb == null || !innerWeb.Exists)
{
throw new ArgumentException("Web does not exist", "innerWeb");
}
web = innerWeb;
webShouldBeClosed = shouldBeClosed;
unsafeUpdatesSetting = innerWeb.AllowUnsafeUpdates;
AllowUnsafeUpdates();
webUrl = web.Url;
}
/// <summary>
/// Creates a new <see cref="SPWeb"/> object using the
/// url of the <see cref="web"/> parameter and wraps it
/// in a new wrapper object. The web will be
/// closed when the wrapper is disposed.
/// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
/// </summary>
/// <remarks>Use this to create a clone of the context web.</remarks>
/// <param name="web">The web to clone.</param>
/// <returns>A new wrapper object.</returns>
public static WebWrapper CloneOf(SPWeb web)
{
using (SPSite site = new SPSite(web.Url))
{
return new WebWrapper(site.OpenWeb());
}
}
/// <summary>
/// Creates a new <see cref="SPWeb"/> object using the
/// <see cref="Web"/> of the <see cref="web"/> parameter and wraps it
/// in a new wrapper object. The web will be
/// closed when the wrapper is disposed.
/// </summary>
/// <remarks>Use this to create a clone of the context web.</remarks>
/// <param name="web">The wrapper to clone.</param>
/// <returns>A new wrapper object.</returns>
public static WebWrapper CloneOf(WebWrapper web)
{
return CloneOf(web.Web);
}
/// <summary>
/// Sets the AllowUnsafeUpdates property to true on the
/// wrapped web object.
/// <remarks>
/// The setting is resat back in the dispose method, unless the
/// web itself is closed.
/// </remarks>
/// </summary>
public void AllowUnsafeUpdates()
{
Web.AllowUnsafeUpdates = true;
}
/// <summary>
/// Returns the url of the inner web.
/// </summary>
/// <returns>A value that equals <see cref="Web"/> <see cref="SPWeb.Url"/> property.</returns>
public override string ToString()
{
return webUrl;
}
/// <summary>
/// Returns a new <see cref="WebWrapper"/> object wrapping a new copy of the inner <see cref="Web"/> object.
/// The cloning is done using the <see cref="SPWeb.Url"/>, thus no security context is transferred to the new web.
/// </summary>
/// <remarks>The static method <see cref="CloneOf(SPWeb)"/> is used on the <see cref="Web"/> property.</remarks>
/// <returns>A new wrapper.</returns>
public WebWrapper Clone()
{
return CloneOf(Web);
}
/// <summary>
/// Implicitly wraps the web object in a <see cref="WebWrapper"/> object.
/// </summary>
/// <param name="web">The web to wrap.</param>
/// <returns>A new wrapper object. The original web may be accessed through the <see cref="Web"/> property.</returns>
public static implicit operator WebWrapper(SPWeb web)
{
return new WebWrapper(web, false);
}
/// <summary>
/// Explicitly extracts the <see cref="Web"/> value from the <see cref="wrapper"/>.
/// </summary>
/// <param name="wrapper">The object wrapping the <see cref="SPWeb"/> to extract.</param>
/// <returns>The inner <see cref="Web"/> of <see cref="wrapper"/>.</returns>
/// <remarks>The returned <see cref="SPWeb"/> object should be properly disposed after use.</remarks>
public static explicit operator SPWeb(WebWrapper wrapper)
{
wrapper.DoNotDisposeInnerWeb();
return wrapper.Web;
}
/// <summary>
/// Wrapper method for <see cref="SPWeb.GetList"/> on <see cref="Web"/> object.
/// </summary>
/// <param name="uri">A site relative uri to the list.</param>
/// <returns>A list if found.</returns>
public SPList GetList(Uri uri)
{
return web.GetList(uri.ToString());
}
/// <summary>
/// Wrapper method for <see cref="SPWeb.GetSiteData"/> on <see cref="Web"/> object.
/// </summary>
/// <returns>The results of the query,</returns>
public DataTable GetSiteData(SPSiteDataQuery query)
{
return Web.GetSiteData(query);
}
/// <summary>
/// Creates a new <see cref="SPWeb"/> as a sub web to this.
/// </summary>
/// <param name="url">The proposed local url of the new web. The nearest available is selected.</param>
/// <param name="name">The title of the new web.</param>
/// <param name="description">The description of the new web.</param>
/// <param name="language">The language of the new web. <remarks>If the language is not supported, the language of this is used.</remarks></param>
/// <param name="template">The site template to use.</param>
/// <returns>The new web wrapped in a new <see cref="WebWrapper"/> object.</returns>
[DebuggerStepThrough]
//debugger step through is to prevent this method to break when debugging, as it throws exceptions by [poor] design.
public WebWrapper CreateSubWeb(string url, string name, string description, uint language,
string template)
{
SPWeb newWeb;
try
{
newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, language, template, true, false);
}
catch (SPException err)
{
if (err.ErrorCode == -2130575266)
{
//language not supported. Fallback to parent web language
newWeb = Web.Webs.Add(findSuitableWebUrl(url), name, description, Web.Language, template, true,
false);
}
else
throw;
}
return new WebWrapper(newWeb);
}
private string findSuitableWebUrl(string proposedName)
{
StringCollection names = new StringCollection();
names.AddRange(Web.Webs.Names);
int suffixIndex = 0;
const int maxIterations = 100000;
string name = proposedName;
while (names.Contains(name) && suffixIndex < maxIterations)
{
name = string.Format("{0}_{1}", proposedName, suffixIndex++);
}
return name;
}
/// <summary>
/// Calling this method will inhibit the default behaviour of closing the web on disposal.
/// </summary>
/// <remarks>Use with caution.</remarks>
internal void DoNotDisposeInnerWeb()
{
webShouldBeClosed = false;
}
}
}

Resources