Authenticating Website Members as Users in CKFinder v3 - c#

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.

Related

XML File produced in a .NET web app works locally, but the content is empty in production

I have a .NET 7 web app, where I have a controller that results in a sitemap.xml file. When I run the application locally, I get an XML file as a result with this content:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>
And it looks like this:
However, when this is pushed to production (everything is hosted as a web app on Azure), the same endpoint returns nothing. It does recognize the endpoint and looks like this:
My code to generate this, is shown below:
[Route("/sitemap.xml")]
public async Task SitemapXml()
{
var countries = await _countryService.GetBySpecificationAsync(new CountrySpecification()
{
Take = int.MaxValue
});
Response.ContentType = "application/xml";
using (var xml = XmlWriter.Create(Response.Body, new XmlWriterSettings { Indent = true }))
{
xml.WriteStartDocument();
xml.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
xml.WriteEndElement();
}
}
My question:
I am completely lost. At first I thought it was because I didn't add support for static files and this is considered a static file, but I do have:
app.UseStaticFiles();
In the Program.cs.
Any hints where I should be starting?
I spent some time this week wanting to answer this question, and I have time now.
The main issue with your attempt is you are not returning XML results. To do so I suggest using IActionResult interface.
Now time to create sitemap.xml. IMO there are 2 ways to go from here, either using a library OR writing your own sitemap method.
I will start with a library. For instance, there is a very simple library (NuGet) called SimpleMvcSitemap.Core. Install it in your project, and in your controller insert the following code:
[Route("/sitemap.xml")]
public async Task<IActionResult> SitemapXml()
{
// your await call etc
List<SitemapNode> nodes = new List<SitemapNode>
{
new SitemapNode(Url.Action("Index","Home")),
new SitemapNode(Url.Action("About","Home")),
//other nodes
};
return new SitemapProvider().CreateSitemap(new SitemapModel(nodes));
}
Btw for this test, I created an asp.net MVC .net 7 project.
I have deployed the solution to azure and it works both on local development and on azure. Here is the result:
If you do want to do it manually, you can do following
var listUrls = new List<string>
{
Url.Action("Index", "Home"),
Url.Action("About", "Home")
};
return new SitemapResult(listUrls);
And here is the implementation:
public class SitemapResult : ActionResult
{
private readonly IEnumerable<string> _urls;
public SitemapResult(IEnumerable<string> urls)
{
_urls = urls;
}
public override async Task ExecuteResultAsync(ActionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var response = context.HttpContext.Response;
response.ContentType = "application/xml; charset=utf-8";
var settings = new XmlWriterSettings() { Async = true, Encoding = Encoding.UTF8, Indent = false };
using (var writer = XmlWriter.Create(response.Body, settings))
{
WriteToXML(writer);
await writer.FlushAsync();
}
}
private void WriteToXML(XmlWriter writer)
{
writer.WriteStartDocument();
// Write the urlset.
writer.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
// url element
foreach (var item in _urls)
{
writer.WriteStartElement("url");
// loc
writer.WriteStartElement("loc");
writer.WriteValue(item);
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
The manual way is also deployed on azure and works, but in the manual way you need to do a lot of work that is already done in a library. To be fair both above outcome is inspired form the question How to dynamically create a sitemap.xml in .NET core 2?.
from this msdn magazine: "A controller that returns void will produce an EmptyResult." I assume this holds true also for Task.
So maybe you need to change your return type of your method from Task to Task<IActionResult> (or whatever suits you most) and return the content with any of these availablle methods.
Then though, I cannot understand why without these mods is currently working locally.

Credentials - Microsoft reporting library

I have problem with Microsoft.Reporting library. I need to access SQL reporting services and I have report name, address of the server and username and password. First of all I need to get all parameters needed for one specific report.
Here is my implementation so far :
using System;
using System.Collections.Generic;
using Microsoft.Reporting.WinForms;
namespace ConsoleApp1
{
class Program
{
static IEnumerable<DataSourceCredentials> CredentialsEnumerable()
{
var credentials = new DataSourceCredentials
{
Name = #"domain\account",
Password = #"password"
};
yield return credentials;
}
static void Main(string[] args)
{
var credentials = new DataSourceCredentials
{
Name = #"domain\account",
Password = #"password"
};
var report = new ReportViewer
{
ProcessingMode = ProcessingMode.Remote
};
report.ServerReport.ReportPath = #"/Archiv/Daily sales";
report.ServerReport.ReportServerUrl = new Uri(#"http://serverIPaddress/reportserver");
report.ServerReport.SetDataSourceCredentials(credentials);
foreach (var param in report.ServerReport.GetParameters())
{
Console.WriteLine(param.ToString());
}
Console.ReadLine();
}
}
}
But I have problem with my code and mainly with :
report.ServerReport.SetDataSourceCredentials(credentials);
I am getting error, that it´s not possible to transfer from Microsoft.Reporting.WinForms.DataSourceCredentials to System.Collections.Generic.IEnumerable
I have already tryid to use variable "credentials" and created IEnumerable class but it´s not working.
Can you please suggest what is wrong with my code? How to fix it and provide credentials for reporting server? Without credentials I am getting error "Not authorized"
Thank you in advance
Problem solved - wrong credentials parameter.
Instead of :
report.ServerReport.SetDataSourceCredentials(credentials);
I used
report.ServerReport.ReportServerCredentials.NetworkCredentials = credentials;
And it´s working !

Check if Raven Db exist?

how do I check programmatically if my Raven Db (http://ravendb.net/) called "Test" exist?
Best regards
EnsureDatabaseExists is an extension method on IDatabaseCommands defined in the Raven.Client.Extensions namespace.
To make it work you need to add a using statement for this namespace.
using Raven.Client;
using Raven.Client.Extensions;
using (DocumentStore store = new DocumentStore()
{
Url = "http://localhost:8080/" ;
})
{
store.Initialize();
store.DatabaseCommands.EnsureDatabaseExists("SomeDatabase");
}
This is an extensions method I use for that:
public static bool DatabaseExists(this IDocumentStore documentStore,
string databaseName)
{
var headers = documentStore.DatabaseCommands.Head("Raven/Databases/" + databaseName);
return headers != null;
}
Easily called:
bool exists = documentStore.DatabaseExists("foo");
This works when your documentStore is pointed at raven's default
system database. If you set a DefaultDatabase on the document store,
I don't believe it would work properly.

Programmatically setting the 'Connect as' user in IIS7 using C#

I'm attempting to do this using the following code snippet, but the FindElement keeps giving me errors indicating it doesn't exist in the current context. Ultimately what I'm trying to do is set the username and password the website uses in the connect as area. This is different from the impersonation user.
using Microsoft.Web.Administration;
using Microsoft.Web.Management;
using Microsoft.Web.Media.TransformManager.Common;
using Microsoft.Web.Media.TransformManager;
using System.Web.Configuration;
using System.Collections;
Configuration config = iisManager.GetApplicationHostConfiguration();
ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites");
ConfigurationElementCollection sitesCollection = sitesSection.GetCollection();
ConfigurationElement siteElement = FindElement(sitesCollection, "site", "name", #"Default Web Site");
ConfigurationElementCollection applicationCollection = siteElement.GetCollection();
ConfigurationElement applicationElement = FindElement(applicationCollection, "application", "path", #"/MyNewVirtualDir");
ConfigurationElementCollection virtualDirCollection = applicationElement.GetCollection();
ConfigurationElement virtualDirElement = FindElement(virtualDirCollection, "virtualDirectory", "path", #"/");
virtualDirElement.Attributes["userName"].Value = "MYDOMAIN\\MyUser";
virtualDirElement.Attributes["password"].Value = "MyPassword";
EDIT : So as I was staring at the question after beating my head against this for a few days, I discovered you can accomplish this using ServerManager in the following context.
ServerManager iisManager = new ServerManager()
site = iisManager.Sites.FirstOrDefault(a => a.Name.Contains("Default"));
site.VirtualDirectoryDefaults.Password = tbImpersonatorPassword.Text;
site.VirtualDirectoryDefaults.UserName = tbImpersonatorUser.Text;
So as I was staring at the question after beating my head against this for a few days, and apparently you can accomplish this using Servermanager in the following context.
ServerManager iisManager = new ServerManager()
site = iisManager.Sites.FirstOrDefault(a => a.Name.Contains("Default"));
site.VirtualDirectoryDefaults.Password = tbImpersonatorPassword.Text;
site.VirtualDirectoryDefaults.UserName = tbImpersonatorUser.Text;
Setting the Username and Password on the VirtualDirectoryDefaults may not yield the results you are looking for. Instead you may want to locate the app within this Site object whose path is the root (hence the .Path.Equals("/") filter on the query), then modify that apps Virtual Directory username and password.
This can be accomplished with the following method (Please Note: this method assumes that you have already located the desired Site via a search on the ServerManagers Sites collection and that you are passing that Site object into this method). Be sure to dispose of the ServerManager object when you are done in order to avoid a memory leak.
public static void SetConnectAsAccount(Site site, string username, string password)
{
if (site == null)
{
throw new ArgumentNullException("site");
}
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException("username");
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException("password");
}
foreach (var app in site.Applications.Where(c => c.Path.Equals("/")))
{
try
{
// set the Connect-As Accounts login credentials to the Service Acount
var appVirDir = app.VirtualDirectories.Where(c => c.Path.Equals("/")).FirstOrDefault();
if (appVirDir != null)
{
appVirDir.UserName = username;
appVirDir.Password = password;
}
}
catch (Exception ex)
{
// log your exception somewhere so you know what went wrong
}
}
}

Amazon API, Product Advertising API , ItemSearch, C#

I'm trying to get use the new product amazon API to search for products on Amazon. I've been looking at their sample code and other people's examples of this but I'm not getting back any results and wondering if anyone else has used the API recently and could provide some assistance?
using System;
using System.ServiceModel;
using Simple.Amazon.ECS;
namespace Simple {
class Program {
// your Amazon ID's
private const string accessKeyId = "*******************";
private const string secretKey = "************************************";
// the program starts here
static void Main(string[] args) {
// create a WCF Amazon ECS client
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
binding.MaxReceivedMessageSize = int.MaxValue;
AWSECommerceServicePortTypeClient client = new AWSECommerceServicePortTypeClient(
binding,
new EndpointAddress("https://webservices.amazon.com/onca/soap?Service=AWSECommerceService"));
// add authentication to the ECS client
client.ChannelFactory.Endpoint.Behaviors.Add(new AmazonSigningEndpointBehavior(accessKeyId, secretKey));
// prepare an ItemSearch request
ItemSearchRequest request = new ItemSearchRequest();
request.SearchIndex = "Books";
request.Title = "WCF";
request.ResponseGroup = new string[] { "Small" };
ItemSearch itemSearch = new ItemSearch();
itemSearch.Request = new ItemSearchRequest[] { request };
itemSearch.AWSAccessKeyId = accessKeyId;
// issue the ItemSearch request
ItemSearchResponse response = client.ItemSearch(itemSearch);
// write out the results
foreach (var item in response.Items[0].Item) {
Console.WriteLine(item.ItemAttributes.Title);
}
}
}
}
All the samples/examples are similar to this in structure but when it comes to the foreach loop there are no items returned(Null) so I get a null exception error.
if the solution above still won't work.
try this one..
download the sample code on http://www.falconwebtech.com/post/2010/06/14/Using-WCF-and-SOAP-to-Send-Amazon-Product-Advertising-API-Signed-Requests.aspx
we need to update service references, make little change at app.config, program.cs, and reference.cs.
app.config:
(1.) appSettings tag;
assign accessKeyId and secretKey value,
add .
(2.) behaviours tag -> endpointBehaviors tag -> behaviour tag -> signingBehavior tag;
assign accessKeyId and secretKey value.
(3.) bindings tag -> basicHttpBinding tag; (optional)
delete binding tag except AWSECommerceServiceBindingNoTransport
and AWSECommerceServiceBindingTransport.
(4.) client tag;
delete endpoint tag except AWSECommerceServiceBindingTransport.
program.cs:
add itemSearch.AssociateTag = ConfigurationManager.AppSettings["associateTag"]; before ItemSearchResponse response = amazonClient.ItemSearch(itemSearch);
reference.cs: (open file in service references folder using visual studio)
change private ImageSet[][] imageSetsField; to private ImageSet[] imageSetsField;
change public ImageSet[][] ImageSets {...} to public ImageSet[] ImageSets {...}
finally we can run our program and it will work. good luck..
nb: i use microsoft visual studio 2010.
there will be 1 warning (invalid child element signing behaviour), i think we can ignore it, or if you have any solution please share.. ^^v..
This is a wsdl error, I use link below to fix it:
https://forums.aws.amazon.com/thread.jspa?threadID=86989

Categories