How to do Basecamp-style accounts in Asp.Net Mvc? - asp.net

For an Asp.Net software as a service application, I want to do account based subdomains like Basecamp and the rest of the 37Signals products have. E.g. acme.myapp.com will load the account for that customer and pull back only their information.
This is easy to do in Ruby on Rails, but how would you handle this functionality in ASP.NET MVC and be able to scale to possibly hundreds of accounts?

Maarten Balliauw's blog covered one method extending RouteBase. I think I've also seen a custom route handler used for this.
Also, this StackOverflow question covered the same question, using a more simplistic approach.
I definitely recommend factoring this code out into the routing side rather than embedding the logic to get domain information in your controllers.

We use:
public static string GetSubDomain()
{
string subDomain = String.Empty;
if (HttpContext.Current.Request.Url.HostNameType == UriHostNameType.Dns)
{
subDomain = Regex.Replace(HttpContext.Current.Request.Url.Host, "((.*)(\\..*){2})|(.*)", "$2");
}
if (subDomain.Length == 0)
{
subDomain = "www";
}
return subDomain.Trim().ToLower();
}

It's not very different compared to RoR. Just get the HTTP-Request, take the Host-Value, split it (at each dot) and take the first part to get the subdomain.
string subdomain = requestContext.HttpContext.
Request.Headers["Host"].Split('.')[0];
Then just resolve the subdomain to the Companies account.

Related

Adding a button for google signin using f#/fable/asp.net/react

I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.

Allow access to web application only when my server redirects to it

My question is very very similar to this one
The idea is the following.
I have an app written in Node (specifically Sails.js) it a simple form for invoices.
And another one in Laravel.
So what I want is that the user can only access that form (Sails app) if one Controller of the Laravel app redirects to it.
On the link above it says that I could use sessions but as you can see this are very different applications. So I'm looking for the simplest and best way to do it.
Any advice is well received or if you have some better approach to solve this please let me know. Thanks
Probably the most simple way is to use the referer header in your Sails controller and do a simple comparison check.
For example:
getinvoice : function(req, res, next) {
var referer = req.headers.referer;
if(referer != 'http://somedomain.com/pageallowedtocallgetinvoice'){
return res.forbidden();
} else {
...
}
}

ServiceStack proper way to access routes and avoid markup

I think this question is more about best practices regarding web services and not necessarily limited to ServiceStack only. From what I've read here and on the SS wiki, the 'recommended' way to implement parent-child entities is to break them down via routes.
For example:
/Users/{UserID}
/Users/{UserID}/Entities
Where User is the logged on user, and entities are his/her items. I'm implementing jqueryui autocomplete and here is where I'm suspecting I'm not doing the right thing.
In the script the path needs the Userid, so I have to manually render it in the browser so that it reads:
type: "GET",
url: "svc/users/**8**/entities",
data: { "SearchTerm": request.term, "Format": 'json' },
This smells wrong to me. I have the UserID from the session and I can get it that way. So I wonder if there a better way to access these objects without having to render data directly into markup?
Am I doing this wrong?
On a side note: I know I could place this data in a hidden field and access it via script etc, I am just curious if there is a better/recommended way to do this via sessions while keeping the routes as is.
Generally this is done with another endpoint, Facebook for instance, uses /my/, but you could do what ever you want.
The reason being, it's very likely you will be returning different information for a user about themselves than you share about that user with someone else.
Let's pretend /user/{UserId}/books returns a user's favorite books. If I want to know what someone's favorite books are, I might be interested in the title, and a brief description, but if I want to see (and possibly manage) my list of favorite books then I might want more information, like the day I added the favorite book, or friends of mine that also like the book.
so /user/{UserId}/books returns:
{
"books":[
{ "title":"Hary Potter", "desc":"A boy who is magic..." }
]
}
however /my/books returns:
{
"books":[
{
"title":"Harry Potter",
"desc":"A boy who is magic...",
"friensWhoLikeBook":[
{ "id":1234, "name":"Bob" }
],
"personalCommentsAboutBookNotToBeShared":"This book changed my life..."
}
]
}

Programicatlly visit (all) ASP.Net page(s) in a website?

In the Security model for out ASP.Net website (.Net 3.5) we store the page name:
page.GetType().Name
as the primary key in a database table to be able to lookup if a user has access to a certain page. The first time a page is visited this record is created automatically in the database.
We have exported these database statements to insert scripts, but each time a new page gets created we have to update the scripts, not a huge issue, but I would like to find an automated way to do this.
I created an attribute that I tagged a few pages with and then wrote a small process to get all the objects that have this attribute, through the reflection create an instance and insert the record using the same code to for page records mentioned above:
IEnumerable<Type> viewsecurityPages = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsDefined(typeof(ViewSecurityAttribute),false));
foreach (Type t in viewsecurityPages)
{
object obj = Activator.CreateInstance(t, false);
//clip..(This code just checks if the record already exists in the DB)
if (feature == null)
{
Attribute attb = Attribute.GetCustomAttribute(t, typeof(ViewSecurityAttribute));
if (attb != null)
{
CreateSecurableFeatureForPage((Page)obj, uow, attb.ToString());
}
}
}
The issue is that page.GetType().Name when the page goes through the actual page cycle process is something like this:
search_accounts_aspx
but when I used the activator method above it returns:
Accounts
So the records don't match the in the security table. Is there anyway to programtically "visit" a webpage so that it goes through the actual page lifecycle and I would get back the correct value from the Name parameter?
Any help/reference will be greatly appreciated.
Interesting problem...
Of course there's a (too obvious?) way to programmatically visit the page... use System.Net.HttpWebRequest. Of course, that requires the URI and not just a handle to the object. This is a "how do we get there from here?" problem.
My suggestions would be to simply create another attribute (or use that same one) which stores the identifier you need. Then it will be the same either way you access it, right?
Alternatively... why not just use a 3rd party web spider/crawler to crawl your site and hit all the pages? There are several free options. Or am I missing something?

Querying the Global Address List (GAL) for users across domains

I need to search for all users containing a certain text string in their name against the Exchange Server Global Address List. This operation will be performed from an ASP.NET application. Note that the GAL is required (not Active Directory) as it contains users across domains. It's also what the customer requested.
I've been looking at Exchange Web Services and Outlook Web Access methods. However neither may be configured in my organisation so I need to know which option is the right one before asking infrastructure to configure it.
Exchange Web Services
I hoped to use the ResolveNames method in Exchange Web Services. The documentation for it states that:
Active Directory is searched first, and then the user's contact folder is searched.
It appears to imply that this method will only return users from the current domain. Is this correct?
Outlook Web Access
The other option I found was GALFind. This looks perfect but this article stated that it is unsupported. It is confirmed as no longer available in this Technet article.
Can anyone please give advice on these or any other options?
It is possible to query across domains by starting the query from the forest root. Here is the code I ended up using:
string filter = "(&(objectCategory=person)(objectClass=user)(name=*" + search + "*))";
var rootEntry = new DirectoryEntry("GC:");
foreach (DirectoryEntry entry in rootEntry.Children)
{
DirectoryEntry forestEntry = entry;
DirectorySearcher searcher = new DirectorySearcher
{
SearchRoot = forestEntry,
Filter = filter,
Sort =
{
Direction = SortDirection.Ascending,
PropertyName = "cn"
}
};
searcher.PropertiesToLoad.AddRange(ADProperties.Values.ToArray());
SearchResultCollection results = searcher.FindAll();
foreach (SearchResult result in results)
{
DirectoryEntry foundEntry = result.GetDirectoryEntry();
// Do something
}
}
I'm no Exchange expert but can't you do this using Collaboration Data Objects (CDO) although it might not be too swift.

Resources