Author |
Share Topic Topic Search Topic Options
|
adam
Newbie
Joined: 12-May-2009
Posts: 7
|
Post Options
Quote Reply
Topic: Manager.Login always returns true - why must we throw LoginException? Posted: 12-May-2009 at 3:47pm |
Hi folks,
Calling manager.Login always seems to return true, even if no Principal is returned from the remote IEntityLoginManager.Login method. I seem to remember that the only way to fail a login attempt is to throw a new LoginException. This seems an odd programming paradigm to throw an exception even in an expected case? Anyway, we do not want to throw an exception but simply to return a null Prinicipal from the IEntityLoginManager.Login method. When I did this, lo and behold, manager.Login still returns true! Is there a way to get it to return false other than throwing a LoginException? Thanks.
|
|
GregD
IdeaBlade
Joined: 09-May-2007
Posts: 374
|
Post Options
Quote Reply
Posted: 13-May-2009 at 3:59pm |
Hi, Adam:
You are correct that a call to EntityManager.Login() will always return true unless an exception is encountered. Thus we expect you to call that method from your client code within a Try-Catch block. But even if you fail to do so, no unauthorized user will ever get access to your data through the EntityServer, which I think you would probably agree would be even worse than an application crash.
Suppose, on the other hand, that we were simply to return true or false from the call to EntityManager.Login(), and depend upon you to test the value and respond accordingly. In that case if you happened to forget to test the result – these things happen! -- then we would have that worst-case scenario of unauthorized access to the data.
Since so much is at stake, we prefer to err on the side of greater security.
You could return a null IPrincipal from your IEntityLoginManager.Login() implementation, but as you have observed you would not be able to use the return value of EntityManager.Login() to assess the success of the authentication request. Instead you would need to test the IPrincipal attached by DevForce to the client-side thread; or more, specifically, that IPrincipal’s Identity:
System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated
IsAuthenticated would have a value of false in the case where your IEntityLoginManager.Login() implementation returns a null. (The System.Threading.Thread.CurrentPrincipal that gets set after you return a null IPrincipal from IEntityLoginManager.Login() is not itself null, so you could not test that for nullness.)
Note that returning a null IPrincipal from your IEntityLoginManager.Login() method will also result in an invalid IdeaBlade.EntityModel.SessionBundle. The SessionBundle created by the call to EntityManager.Login() is used to authenticate each subsequent communication between the EntityManager and EntityServer. Thus, the first attempt to access the latter (as by issuing a query) would result in an exception. You would have to be sure not to attempt to access the EntityServer after a failed login.
I personally think it much more advisable to stay with the intended paradigm, call EntityManager.Login() within a try-catch, and treat any sort of exception resulting from the call as a reason not to permit access to your data. FYI, we have included a code sample that implements authentication and role-based authorization security in our most recent release of DevForce. Due to recent changes in many assembly names in DevForce you will need that release to run it; but you could of course simply inspect it without running it.
Edited by GregD - 13-May-2009 at 4:03pm
|
|
adam
Newbie
Joined: 12-May-2009
Posts: 7
|
Post Options
Quote Reply
Posted: 13-May-2009 at 4:12pm |
Hi,
Thanks for the thorough reply and explanation! That certainly does explain your reasoning behind choosing that paradigm. In any event, since some developers here would prefer to not use a try/catch block, I went ahead and implemented a null check of the System.Threading.Thread.CurrentPrincipal instead. What's interesting is that it was in fact null when I returned null from the IEntityLoginManager.Login (in our case, this is an Asp.net application - a windows app may be different and probably is). To be safer, I'll add the further check of System.Threading.Thread.CurrentPrincipal.Identity.IsAuthenticated.
Anyway, I understand your reasoning, and thanks again for the thorough explanation. For what it's worth, I might make the Login method return false in case of a failed login for those of us who would be careful about what to do in such an event, and it might help IdeaBlade have one less inquiry as to why it always returns true ;)... but that is understandably a low priority!
Adam :)
|
|
adam
Newbie
Joined: 12-May-2009
Posts: 7
|
Post Options
Quote Reply
Posted: 20-May-2009 at 2:35pm |
I've come accross another problem now: how do I specify more than the built in types of LoginExceptionType? I need to add two more reasons for login failure that are not supported by IdeaBlade's LoginExceptionTypes... I tried throwing my own home grown exception type, but my type is converted to a generic "IdeaBlade.EntityModel.EntityServerException" so that I cannot catch it specifically, nor does the "IdeaBlade.EntityModel.EntityServerException" store my custom exception in it's InnerException property. What am I to do? I cannot place the failure reason in the returned IPrincipal because there is no returned IPrincipal when authentication fails... (the reasons I need to support are "temporary password expired" and "account is locked")... Any advice would be appreciated!
|
|
GregD
IdeaBlade
Joined: 09-May-2007
Posts: 374
|
Post Options
Quote Reply
Posted: 21-May-2009 at 11:29am |
Adam:
Using the Console App version of the Security code solution that ships with the current version of DevForce, I just tried throwing my own "home grown" exception from the IEntityLoginManager.Login() method:
...
ExitingSuccessfully();
throw new Exception("Something went horribly wrong");
return principal; }
When this exception gets back to the client-side calling program (in the Catch block), it is the InnerException of the InnerException (pException.InnerException.InnerException.Message) that contains my message "Something went horribly wrong".
|
|
adam
Newbie
Joined: 12-May-2009
Posts: 7
|
Post Options
Quote Reply
Posted: 21-May-2009 at 4:42pm |
Hi,
Thanks for the reply... your solution did not work for me??:
1) My inner exception is null (and so is the InnerException.InnerException) - i'm running in 3-tier mode...
2) Even if it wasn't null, I have no way of programmatically catching my custom error type... the following code fails:
catch(MyCustomException customException)
{
//... do something...
}
I tried inheriting MyCustomException from LoginException. That did not work: it treated my exception on the client side as a plain LoginException... Casting it to MyCustomException type fails.
Without a strongly typed way of knowing my exception, I might have to resort to parsing the .Message string property, which feels very hacky! Any further suggestions?
|
|
GregD
IdeaBlade
Joined: 09-May-2007
Posts: 374
|
Post Options
Quote Reply
Posted: 21-May-2009 at 6:58pm |
Adam, I've duplicated your issue and run it by one of our senior developers, who said it looks like a bug. I've opened a Defect Report on it and she'll be taking a look at it to see what can be done.
|
|
adam
Newbie
Joined: 12-May-2009
Posts: 7
|
Post Options
Quote Reply
Posted: 23-Feb-2010 at 9:30am |
Hi,
Is there any news on this particular issue (the last issue above)? It probably was addressed, by I wanted to know if I can cast the exception to my custom type on the client side. My code simply "parses" the message text to identify the type of exception, but now I'm going back and refactoring that old code and wondering if I can fix that code to catch a specific error type? Do I need to cast the Exception or the Exception's Inner Exception? Or InnerException.InnerException for that matter? Thanks!
|
|
ting
IdeaBlade
Joined: 27-Mar-2009
Location: San Francisco
Posts: 427
|
Post Options
Quote Reply
Posted: 07-Apr-2010 at 5:35pm |
Yes, custom LoginExceptions can now be caught by the client. This was fixed in 5.1.1.
|
|