Saving log to Azure Storage using Log4net from MVC - c#

I have a MVC application which is a public site. I am using Log4net to store user's activity in Azure Storage.
I am storing contextual data in threadcontext and use this when message are logged into azure storage.
But for some reason it is taking the wrong values i.e some random values. I doubt that i am doing something wrong using this threadcontext.
Please find below my code snippet.
In my Mvc controller i am using
log4net.ThreadContext.Properties["CustomerID"] = username
log4net.ThreadContext.Properties["CreatedBy"] = createdbyusername
In custom Appender i am using as
var keys = log4net.ThreadContext.Properties.GetKeys();
CustomerID = keys.Contains("CustomerID") ? log4net.ThreadContext.Properties["CustomerID"].ToString() : loggingEvent.Identity,
CreatedBy = keys.Contains("CreatedBy") ? log4net.ThreadContext.Properties["CreatedBy"].ToString() : loggingEvent.Identity,
Can anyone tell me how to achieve this so that it takes the right value for each request?
My appender code:
protected void LogAudit(AuditEventType eventType, object additionalObject, string customerId = "")
{
if (_logger != null)
{
if (!String.IsNullOrEmpty(customerId))
log4net.ThreadContext.Properties["CustomerID"] =
customerId;
AuthSSOProvider sso = new AuthSSOProvider();
var helpDeskUser = sso.GetCookie(ConfigurationManager.AppSettings.Get("cookieKey"));
if (helpDeskUser != null)
log4net.ThreadContext.Properties["CreatedBy"] =
helpDeskUser;
_logger.LogAudit(eventType, additionalObject);
}
}

Related

Decrypt ".AspNetCore.Session" cookie in ASP.NET Core

In Asp.Net core, a cookie is created when you configure your app to app.UseSession().
By default the cookie is called ".AspNetCore.Session". Its value identifies the session to be used. Currently, I'm saving my session data on a sql server. I need to know the decrypted value of ".AspNetCore.Session" so that I can lookup the session in the database.
Is there a way to decrypt this value? I know ASP.NET must do it behind the scenes somehow.
The session source has everything, but you should need to know it, ISessionStore and IDistributedSessionStore gives you a sessionkey to use.
Rather than make an assumption about the cookie format, what is stopping you from using the store APIs?
I had to extract the private Pad function from Microsoft.AspNetCore.Session, but I was able to get what I needed:
public class DiscussionController : Controller
{
private readonly IDataProtector _dataProtector;
public DiscussionController(IDataProtectionProvider dataProtectionProvider)
{
var protectorPurpose = "whatever purpose you want";
_dataProtector = dataProtectionProvider.CreateProtector(protectorPurpose);
}
public IActionResult Index()
{
HttpContext.Request.Cookies.TryGetValue(".AspNetCore.Session", out string cookieValue);
var protectedData = Convert.FromBase64String(Pad(cookieValue));
var unprotectedData = _dataProtector.Unprotect(protectedData);
var humanReadableData = System.Text.Encoding.UTF8.GetString(unprotectedData);
return Ok();
}
private string Pad(string text)
{
var padding = 3 - ((text.Length + 3) % 4);
if (padding == 0)
{
return text;
}
return text + new string('=', padding);
}
}
The Pad function was taken from: https://github.com/aspnet/AspNetCore/blob/87629bbad906e9507026692904b6bcb5021cdd33/src/Middleware/Session/src/CookieProtection.cs#L61-L69

Get a list of active session from RedisSessionStateProvider

In my Azure ASP.NET MVC website I want to display how many clients are connected to a Redis sessions state provider and how long they are active. I use the aspnet-redis-providers lib on the Azure Github.
In Redis, it creates a {[app_name]_[sessionkey}_Internal key with a SessionTimeout key with the value of the configured session timeout. The EXPIRE for that key is set to that time and when you check to TTL for the key you see the session access.
How can I use the session state provider library to access this information? If that is not possible, is there any other library I can use to query this info safely, without interfering with the session state provider?
Here is what I was able to do. I created my own session object collection and grabbed all keys (which I am putting in DB 1) then I loop through all keys and grab the TTL.
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Newtonsoft;
using StackExchange.Redis.Extensions.Core;
using System.Linq;
private static Lazy<ConnectionMultiplexer> conn = new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(ConfigurationManager.AppSettings["RedisServerMaster"]
+ "," + ConfigurationManager.AppSettings["RedisServerSlave"]
+ "," + ConfigurationManager.AppSettings["RedisOptions"])
public class SessionObjects
{
public string SessionId { get; set; }
public TimeSpan? TTL { get; set; }
}
List<SessionObjects> lso = new List<SessionObjects>();
var serializer = new NewtonsoftSerializer();
StackExchangeRedisCacheClient cacheClient;
cacheClient = new StackExchangeRedisCacheClient(rConn, serializer, 1);
IEnumerable<string> keys = cacheClient.SearchKeys("*");
var db = rConn.GetDatabase(1);
foreach (var s in keys)
{
SessionObjects so = new SessionObjects();
so.SessionId = s;
so.TTL = db.KeyTimeToLive(s);
lso.Add(so);
}

Authenticating Website Members as Users in CKFinder v3

Before beginning this question, I should point out that my knowledge of ASP.NET & C# is pretty much nil.
I'm in the process of trying to integrate the ASP.NET version of CKFinder v3 into a site built in a different language and all is going well so far; I have everything setup as I want it and it's working when I grant unrestricted access to CKF but I'm stuck at the point now of trying to restrict access to it by authenticating only certain members of my site to use it. All the pages that CKFinder appears on on my site are only accessible by those certain members but I need an extra level of security if, for example, anyone figures out the direct path to my "ckfinder.html" file.
In the ASP version of CKFinder, I simply added this line in the function that checks my member's privileges, where isEditor was a boolean whose value was assigned per member based on information from my database:
session("accessckf")=isEditor
and then edited the CheckAuthentication() function in CKFinder's "config.asp" file to read:
function CheckAuthentication()
CheckAuthentication=session("accessckf")
end function
Reading through this "Howto", authentication seems to be much more complex in v3 but, after a lot of trial and error and some help from Lesiman, I created this C# file, which is located in my CKF directory:
<%#page codepage="65001" debug="true" language="c#" lcid="6153"%>
<%#import namespace="CKSource.CKFinder.Connector.Core"%>
<%#import namespace="CKSource.CKFinder.Connector.Core.Authentication"%>
<%#import namespace="CKSource.CKFinder.Connector.Core.Builders"%>
<%#import namespace="CKSource.CKFinder.Connector.Host.Owin"%>
<%#import namespace="Owin"%>
<%#import namespace="System.Data.Odbc"%>
<%#import namespace="System.Threading"%>
<%#import namespace="System.Threading.Tasks"%>
<script runat="server">
public void Configuration(IAppBuilder appBuilder){
var connectorBuilder=ConfigureConnector();
var connector=connectorBuilder.Build(new OwinConnectorFactory());
appBuilder.Map("/path/to/connector",builder=>builder.UseConnector(connector));
}
public ConnectorBuilder ConfigureConnector(){
var connectorBuilder=new ConnectorBuilder();
connectorBuilder.SetAuthenticator(new MyAuthenticator());
return connectorBuilder;
}
public class MyAuthenticator:IAuthenticator{
public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest,CancellationToken cancellationToken){
var domain=HttpContext.Current.Request.Url.Host;
var cookie=HttpContext.Current.Request.Cookies[urlDomain];
var password="";
var username="";
var user=new User(false,null);
if (cookie!=null){
if (cookie["username"]!=null)
username=cookie["username"];
if (cookie["password"]!=null)
password=cookie["password"];
if(username!=""&&password!=""){
var connection=new OdbcConnection("database=[database];driver=MySQL;pwd=[pwd];server=[server];uid=[uid];");
connection.Open();
OdbcDataReader records=new OdbcCommand("SELECT ISEDITOR FROM MEMBERS WHERE USERNAME='"+username+"' AND PASSWORD='"+password+"'",connection).ExecuteReader();
if(records.HasRows){
records.Read();
bool isEditor=records.GetString(0)=="1";
var roles="member";
if(isEditor)
roles="editor,member";
user=new User(isEditor,roles.Split(','));
}
records.Close();
connection.Close();
}
}
return Task.FromResult((IUser)user);
}
}
</script>
Loading that page produces no errors (which doesn't necessarily mean it's working as trying to write anything to screen from within the public class doesn't work, for some reason) so now I'm at the stage of somehow checking that file for authentication.
Originally, I had tried loading it via XMLHttp from within my function that checks membership privileges for the site but, as I suspected and as Lesmian confirmed, that wouldn't work. After more trial & error, I added code to check website member privileges to the C# file, which leads me to where I am now: stuck!
What do I need to edit within CKFinder in order to have it use this custom file to check whether or not a user is authenticated?
First you'll need a connector between the ASP's Session and CKFinder's .Net authenticator. Here's an example that serializes ASP Session contents into JSON.
Put the connector.asp into a publicly accessible location. http://myaspwebsite.com/connector.asp for example.
connector.asp
<%#Language=VBScript CodePage=65001%>
<% Option Explicit %>
<!--#include file="JSON.asp"-->
<%
' obtain JSON.asp from https://github.com/tugrul/aspjson/archive/master.zip
' just for testing, must be removed in the production environment
Session("isEditor") = True
Session("isMember") = True
' only local requests allowed
' instead of local and remote ip comparison, a secret key can be used
If Request.ServerVariables("LOCAL_ADDR") <> Request.ServerVariables("REMOTE_ADDR") Then
Response.Status = "403 Forbidden"
Response.End
End If
Response.ContentType = "application/json"
Response.Charset = "utf-8"
Dim JSONObject, Key
Set JSONObject = jsObject()
For Each Key In Session.Contents
If Not IsObject(Session.Contents(Key)) Then 'skip the objects cannot be serialized
JSONObject(Key) = Session.Contents(Key)
End If
Next
JSONObject.Flush
%>
CKFinder 3.3.0 comes with a default connector which can be found in /ckfinder/bin/CKSource.CKFinder.Connector.WebApp.dll, remove it.
Examine the following program and remember to replace builder.Map("/connector", SetupConnector); and new Uri("http://myaspwebsite.com/connector.asp"); with your own values.
It simply authenticates the users by checking ASP Session varaibles isEditor and isMember via connector.asp and finally claims the roles editor , member or none.
I assume that you have configured the roles editor and member in the web.config.
Then put the Shaggy.cs into /ckfinder/App_Code. Create App_Code directory if not exist. .Net files in this folder will be compiled on the fly.
For more information have a look at Shared Code Folders in ASP.NET Web Projects
Shaggy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Newtonsoft.Json.Linq;
using Owin;
[assembly: Microsoft.Owin.OwinStartup(typeof(CKSource.CKFinder.Connector.Shaggy.Startup))]
namespace CKSource.CKFinder.Connector.Shaggy
{
using FileSystem.Local;
using FileSystem.Dropbox;
using Core;
using Core.Authentication;
using Config;
using Core.Builders;
using Core.Logs;
using Host.Owin;
using Logs.NLog;
using KeyValue.EntityFramework;
public class Startup
{
public void Configuration(IAppBuilder builder)
{
LoggerManager.LoggerAdapterFactory = new NLogLoggerAdapterFactory();
RegisterFileSystems();
builder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
AuthenticationMode = AuthenticationMode.Active
});
//replace connector path with yours
builder.Map("/connector", SetupConnector);
}
private static void RegisterFileSystems()
{
FileSystemFactory.RegisterFileSystem<LocalStorage>();
FileSystemFactory.RegisterFileSystem<DropboxStorage>();
}
private static void SetupConnector(IAppBuilder builder)
{
var keyValueStoreProvider = new EntityFrameworkKeyValueStoreProvider("CacheConnectionString");
var authenticator = new ShaggysAuthenticator();
var connectorFactory = new OwinConnectorFactory();
var connectorBuilder = new ConnectorBuilder();
var connector = connectorBuilder
.LoadConfig()
.SetAuthenticator(authenticator)
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
config.SetKeyValueStoreProvider(keyValueStoreProvider);
})
.Build(connectorFactory);
builder.UseConnector(connector);
}
}
public class ShaggysAuthenticator : IAuthenticator
{
// this method makes an http request on the background to gather ASP's all session contents and returns a JSON object
// if the request contains ASP's session cookie(s)
private static JObject GetAspSessionState(ICommandRequest requestContext)
{
// building Cookie header with ASP's session cookies
var aspSessionCookies = string.Join(";",
requestContext.Cookies.Where(cookie => cookie.Key.StartsWith("ASPSESSIONID"))
.Select(cookie => string.Join("=", cookie.Key, cookie.Value)));
if (aspSessionCookies.Length == 0)
{
// logs can be found in /ckfinder/App_Data/logs
LoggerManager.GetLoggerForCurrentClass().Info("No ASP session cookie found");
// don't make an extra request to the connector.asp, there's no session initiated
return new JObject();
}
//replace this URL with your connector.asp's
var publicAspSessionConnectorUrl = new Uri("http://myaspwebsite.com/connector.asp");
var localSafeAspSessionConnectorUrl = new UriBuilder(publicAspSessionConnectorUrl) { Host = requestContext.LocalIpAddress };
using (var wCli = new WebClient())
try
{
wCli.Headers.Add(HttpRequestHeader.Cookie, aspSessionCookies);
wCli.Headers.Add(HttpRequestHeader.Host, publicAspSessionConnectorUrl.Host);
return JObject.Parse(wCli.DownloadString(localSafeAspSessionConnectorUrl.Uri));
}
catch (Exception ex) // returning an empty JObject object in any fault
{
// logs can be found in /ckfinder/App_Data/logs
LoggerManager.GetLoggerForCurrentClass().Error(ex);
return new JObject();
}
}
public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
{
var aspSessionState = GetAspSessionState(commandRequest);
var roles = new List<string>();
var isEditor = aspSessionState.GetNullSafeValue("isEditor", false);
var isMember = aspSessionState.GetNullSafeValue("isMember", false);
if (isEditor) roles.Add("editor");
if (isMember) roles.Add("member");
var isAuthenticated = isEditor || isMember;
var user = new User(isAuthenticated, roles);
return Task.FromResult((IUser)user);
}
}
public static class JObjectExtensions
{
// an extension method to help case insensitive lookups with a default value to get avoid NullReferenceException
public static T GetNullSafeValue<T>(this JObject jobj, string key, T defaultValue = default(T))
{
dynamic val = jobj.GetValue(key, StringComparison.OrdinalIgnoreCase);
if (val == null) return defaultValue;
return (T)val;
}
}
}
Now you should have a working CKFinder connector. Change the logic in the method AuthenticateAsync if you need and see how CKFinder handles your Classic ASP membership management.
Did you setup your custom authentication provider with ConnectorBuilder?
public ConnectorBuilder ConfigureConnector()
{
var connectorBuilder = new ConnectorBuilder();
connectorBuilder.SetAuthenticator(new MyAuthenticator());
return connectorBuilder;
}
You can find full example here: http://docs.cksource.com/ckfinder3-net/configuration_by_code.html.
UPDATE
Additionally you should register ConnectorBuilder inside Startup class to add it to request pipeline:
public void Configuration(IAppBuilder appBuilder)
{
var connectorBuilder = ConfigureConnector();
var connector = connectorBuilder.Build(new OwinConnectorFactory());
appBuilder.Map("/CKFinder/connector", builder => builder.UseConnector(connector));
}
All this is from a documentation link I've provided before.

Session and FormsAuthentication in MVC3 access control

I am using SESSION for storing user data, thus avoiding unnecessary access to the database. However, each access controller, data FormsAuthentication are renewed and data SESSION no.
What better way to get around this problem?
Put the lives of the great SESSION, or make a Base Controller or each called a ActionResult renew the life of SESSION.
Detail, SESSION I use this to mount a header of my pages.
This code, create SESSION.
public static void UsuarioLogar(string login)
{
CustomMembershipUser usuario = new CustomMembershipUser();
using (var dbUser = new ERPContext())
{
var dados = (from u in dbUser.Usuario
where u.Login == login
select new
{
Nome = u.Nome,
UsuarioID = u.UsuarioID,
EmpresaID = u.EmpresaID,
EmpresaLogada = u.Empresa,
PessoaLogada = u.PessoaLogada
}).FirstOrDefault();
if (dados != null)
{
usuario.UsuarioID = dados.UsuarioID;
usuario.Nome = dados.Nome;
usuario.EmpresaID = dados.EmpresaID;
usuario.EmpresaLogada = dados.EmpresaLogada;
usuario.PessoaLogada = dados.PessoaLogada;
if (usuario.PessoaLogada != null)
usuario.Acesso = "Restrito";
else
usuario.Acesso = "Full";
HttpContext.Current.Session["usuarioLogado"] = usuario;
}
else
{
HttpContext.Current.Session["usuario"] = null;
FormsAuthentication.SignOut();
}
}
}
I would avoid Session, especially when using MVC - it isn't a good fit with the web.
One alternative would be to use IIdentity and set the details in the Application_AuthenticateRequest method.

How to create a new user in Tridion 2011 using core services?

I know how to create components, pages, structure group but i got stuck while creating new user using core services of .net?
Can anyone help me out of this?
Something like this should get you started:
public void CreateUser(string userName, string friendlyName, bool makeAdministrator)
{
var defaultReadOptions = new ReadOptions();
using (var client = GetCoreServiceClient())
{
var user = (UserData)client.GetDefaultData(ItemType.User, null);
user.Title = userName;
user.Description = friendlyName;
user.Privileges = makeAdministrator ? 1 : 0;
client.Create(user, defaultReadOptions);
}
}

Categories