How to enable a user for impersonation in Tridion 2009? - api

I'm trying to use Tridion's ContentManagment API to retrieve taxonomy categories and keywords, but I'm running into an Access denied error.
I have the following method:
public Dictionary<string, string> GetKeywords(string tcmUri)
{
var result = new Dictionary<string, string>();
try
{
// _settings.ImpersonationUser = "MYDOMAIN/myusername"
using (var session = new Session(_settings.ImpersonationUser))
{
var category = new Category(new TcmUri(tcmUri), session);
var keywords = category.GetKeywords(new Filter());
if (keywords != null && keywords.Count > 0)
{
foreach (var keyword in keywords)
{
result.Add(keyword.Id.ToString(), keyword.Title);
}
}
}
}
catch (Exception ex)
{
Logger.Log.Error(
"Failed to retrieve keywords for '{0}'.".FormatWith(tcmUri), ex);
}
return result;
}
The user I've got in _settings.ImpersonationUser has access to the Tridion Content Manager, is configured as an administrator, and has been added to Impersonation users in the "SDL Tridion Content Manager configuration" snap-in.
The error I'm getting is the following:
System.Runtime.InteropServices.COMException (0x80040302):
<?xml version="1.0"?>
<tcm:Error xmlns:tcm="http://www.tridion.com/ContentManager/5.0"
ErrorCode="80040302" Category="16" Source="Kernel" Severity="2">
<tcm:Line ErrorCode="80040302" Cause="true" MessageID="16226">
<![CDATA[Access denied for the user MYDOMAIN\myuser.]]
<tcm:Token>MYDOMAIN\myuser</tcm:Token>
</tcm:Line>
<tcm:Details>
<tcm:CallStack>
<tcm:Location>SystemBLST.GetUserContext</tcm:Location>
<tcm:Location>SystemBLST.IBLSecurityST_GetUserContext</tcm:Location>
</tcm:CallStack>
</tcm:Details>
</tcm:Error>
Does anyone have any clues to what I'm doing wrong?
Thanks in advance!

Here's a few things to understand when it comes to impersonation & Tridion...
The user executing the code should not have access to Tridion.
The user executing the code should be configured as a valid "Impersonation User"
The user that the code impersonates should be a valid Tridion user.
If all those 3 conditions are true, impersonation will work.
By executing the code, I mean the Windows account under which the code is being executed. If this account has access to Tridion, you do NOT need to use impersonation.
Hope this helps.

Related

LDAP Authentication for Azkaban

We are trying to setup Azkaban with LDAP authentication in our production environment. Any leads on how to do this? Documentation says it can be done by adding plugin jar file by extending UserManager class . I am a newbie to azkaban , looking for some example code for this
You will need to install a custom "user manager" plugin. It can be found on github: https://github.com/researchgate/azkaban-ldap-usermanager
The instructions on how to configure the user manager plugin can be found on the front page of the github repo.
In essence you will need to:
Download and build the plugin
Copy the .jar file that you built into the ./extlib directory of your Azkaban installation
Edit azkaban.properties file, specifying user.manager.class and a number of user.manager.ldap properties.
We also wanted to setup an LDAP authentication in azkaban - but the open source project mentioned in first answer has very limited capabilities and doesn't allow to start TLS negotiation after the connection to LDAP server is established.
We have written a completely new java class to take care of the following scenarios:
Establish a LDAP connection unsecurely (on port 389)
Start TLS Response and TLS negotiate
And then athenticate the user credentials.
With this approach we also dont have to create a service user in LDAP only for azkaban.
Take a look in the sample code block
#Override
public User getUser(String username, String password) throws UserManagerException {
if (username == null || username.trim().isEmpty()) {
throw new UserManagerException("Username is empty.");
} else if (password == null || password.trim().isEmpty()) {
throw new UserManagerException("Password is empty.");
}
String email = null;
String groups = null;
StringBuilder url = new StringBuilder("ldap://").append(ldapHost).append(":").append(ldapPort);
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url.toString());
try {
// Create initial context
LdapContext ctx = new InitialLdapContext(env, null);
// Start TLS
StartTlsResponse tls = (StartTlsResponse) ctx.extendedOperation(new StartTlsRequest());
SSLSession sess = tls.negotiate();
// Adding username and password to environment
StringBuilder sb = new StringBuilder("uid=").append(username).append(",").append(ldapUserBase);
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "simple");
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, sb.toString());
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
// Search the user in a LDAP directory
final LdapCtx x = (LdapCtx) ctx.lookup(sb.toString());
// Lookup is successful - creating an Azkaban user
User user = new User(username);
// Searching LDAP directory to get email and group
SearchControls ctls = new SearchControls();
String[] attributes = { "cn", "memberOf", "mail" };
ctls.setReturningAttributes(attributes);
NamingEnumeration<?> answer = ctx.search(ldapUserBase, "(uid=" + username + ")", ctls);
Boolean isAdmin = false;
// Search user email and groups
while (answer.hasMore()) {
SearchResult rslt = (SearchResult) answer.next();
Attributes attrs = rslt.getAttributes();
groups = attrs.get("memberof").toString().split(":")[1].trim();
if (attrs.get("memberof") != null && attrs.get("memberof").toString().split(":").length > 0) {
groups = attrs.get("memberof").toString().split(":")[1].trim();
for (String group : groups.split(",")) {
if (ldapAdminGroups.contains(group))
isAdmin = true;
}
}
if (attrs.get("mail") != null) {
email = attrs.get("mail").toString().split(":")[1].trim();
user.setEmail(email);
}
}
// Assign the correct role
if (isAdmin)
user.addRole("admin");
else
user.addRole("read");
ctx.close();
return user;
} catch (NamingException e) {
throw new UserManagerException("LDAP error: " + e.getMessage(), e);
} catch (IOException e) {
// TODO Auto-generated catch block
throw new UserManagerException("IO error", e);
}
}
Note: I haven't done lot of exception handling in this - you need to do it as per your needs.
How to make it make it work in Azkaban:
Build a maven or gradle project.
No additional library is required (except Azkaban - i.e. com.linkedin.azkaban)
Have a new class which will inherit 'azkaban.user.UserManager'
Build and copy the jar in azkaban/extlibs
In azkaban.properties - set "user.manager.class=" and also all required properties like host, port and ldap userbase (ou=Users,dc=stackoverflow,dc=com) details.
And you should be good to authenticate users via LDAP.
Happy Coding !!
Thanks,
Hussain Bohra

Unable to cast object of type System.Security.Claims.ClaimsPrincipal to type Microsoft.IdentityModel.Claims.IClaimsPrincipal

I am developing one MVC4 application which authenticate corp domain users using ADFS and i have written the code and i am getting the error like below, am i not getting claims ?
System.InvalidCastException: Unable to cast object of type 'System.Security.Claims.ClaimsPrincipal' to type 'Microsoft.IdentityModel.Claims.IClaimsPrincipal'.
public ActionResult Index()
{
try
{
IClaimsPrincipal principal = (IClaimsPrincipal)Thread.CurrentPrincipal;
IClaimsIdentity identity = (IClaimsIdentity)principal.Identity;
if (String.IsNullOrEmpty(identity.Claims[0].Value.Split('#')[0]))
{
ViewData["Message"] = string.Format("You are email is :{0}", identity.Claims[0].Value.Split('#')[0]);
}
else
{
ViewData["Message"] = "You are not getting any claims";
}
}
catch (Exception ex)
{
ViewData["Message"] = "Something wrong.";
}
return View();
}
What you observe is result of mixing .NET 3.5 WIF (Microsoft.IdentityModel) and WIF 4.0 (System.IdentityModel & System.Security).
What I suggest is:
Remove reference to Microsoft.IdentityModel.* assemblies in your project
Add reference to System.IdentityModel & System.IdentityModel.Services assemblies
Fix using statements
Fix references to Microsoft.IdentityModel in your Web.Config
Do a backup copy of your project before doing this, because, if you haven't done this before, you might end up with a lot of error and not working code. But the main idea is that you have to get rid of all and any Microsoft.IdentityModel references and you will be good.
I am able resolved this issue as per astaykov suggestion and i changed the code like below,
using System.Security.Claims;
var identity = User.Identity as ClaimsIdentity;
foreach (var claim in identity.Claims)
{
if (claim.Type.Contains("EmailAddress"))
{
ViewBag.EmailName = claim.Value;
}
}

Problems with own AD forms authentication in LS 2011 web application

I've got a problem with a LightSwitch 2011 web application using forms authentication.
I've implemented my own login screen which authenticates the user against the active directory. My code also checks to see if the user is assigned to a specific active directory group to decide if they can add / edit / delete data.
The login form is placed on the Login.aspx page. The button to login holds the following code:
protected void buttonLogin_Click(object sender, EventArgs e)
{
LdapAuthentication authentication = new LdapAuthentication();
try
{
bool isUserAdmin = false;
if (authentication.IsUserAuthenticated(textBoxUserName.Text, textBoxPassword.Text, ref isUserAdmin))
{
FormsAuthenticationTicket authenticationTicket = new FormsAuthenticationTicket(1,
textBoxUserName.Text, DateTime.Now, DateTime.Now.AddSeconds(1), false, String.Empty);
//Encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);
//Create a cookie, and then add the encrypted ticket to the cookie as data.
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
//Add the cookie to the outgoing cookies collection.
Response.Cookies.Add(authCookie);
//If the everyoneAdmin is set to true the validation of the administratorgroup
//is decativated so we have to grant the current user administrator rights
if (everyoneAdmin)
isUserAdmin = true;
Session["isUserAdmin"] = isUserAdmin ;
Response.Redirect("default.htm");
}
}
catch (Exception ex)
{
labelError.Text = ex.Message;
labelError.Visible = true;
textBoxPassword.Text = String.Empty;
}
}
public bool IsUserAuthenticated(String userName, String password, ref bool isUserAdmin)
{
if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
return false;
String domain = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["Domain"]))
domain = Convert.ToString(ConfigurationManager.AppSettings["Domain"]).Trim();
else
throw new NullReferenceException("The Domain in the configuration must not be null!");
String ldpa = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["LDPA"]))
ldpa = String.Format("LDAP://{0}", Convert.ToString(ConfigurationManager.AppSettings["LDPA"]).Trim());
else
throw new NullReferenceException("The LDPA in the configuration must not be null!");
String administrationGroup = String.Empty;
if (!String.IsNullOrEmpty(ConfigurationManager.AppSettings["AdministratorGroup"]))
administrationGroup = Convert.ToString(ConfigurationManager.AppSettings["AdministratorGroup"]).Trim();
else
throw new NullReferenceException("The AdministrationGroup in the configuration must not be null!");
String domainUserName = String.Format(#"{0}\{1}", domain.Trim(), userName.Trim());
DirectoryEntry directoryEntry = new DirectoryEntry(ldpa, domainUserName, password);
try
{
//Bind to the native AdsObject to force authentication.
object obj = directoryEntry.NativeObject;
DirectorySearcher directorySearcher = new DirectorySearcher(directoryEntry);
directorySearcher.Filter = String.Format("(SAMAccountName={0})", userName.Trim());
directorySearcher.PropertiesToLoad.Add("cn");
directorySearcher.PropertiesToLoad.Add("memberOf");
SearchResult directorySearchResult = directorySearcher.FindOne();
//unable to find a user with the provided data
if (directorySearchResult == null)
return false;
if (directorySearchResult.Properties["memberof"] != null)
{
//If the memberof string contains the specified admin group
for (int i = 0; i < directorySearchResult.Properties["memberof"].Count; i++)
{
string temp = directorySearchResult.Properties["memberof"].ToString();
// get the group name, for example:
if (directorySearchResult.Properties["memberof"].ToString().ToLower().Contains(administrationGroup.ToLower()))
{
isUserAdmin = true;
break;
}
}
}
}
catch (Exception ex)
{
throw new Exception(String.Format("Error authenticating user.\n\rMessage:\n\r {0}", ex.Message));
}
return true;
}
In the class which holds the CanExcecute (server tier) methods I've implemented the following method:
public bool IsCurrentUserAdmin()
{
if (HttpContext.Current.Session["isUserAdmin"] == null)
return false;
return (bool)(HttpContext.Current.Session["isUserAdmin"]);
}
For example, the CanExcecute methods for one table
partial void dtFacilities_CanDelete(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
partial void dtFacilities_CanInsert(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
partial void dtFacilities_CanUpdate(ref bool result)
{
result = this.IsCurrentUserAdmin();
}
WebConfig
<authentication mode="Forms">
<form>s name=".ASPXAUTH"
loginUrl="Login.aspx"
protection="All"
timeout="30"
path="/"
requireSSL="false"
slidingExpiration="true"
defaultUrl="Home.aspx"
cookieless="UseUri" />
</authentication>
<authorization>
<deny users="?">
</deny></authorization>
Problems:
The problem is that if the user is idle for longer than the timeout the session times out. So, the session token isUserAdmin is NULL. At this point I want the application to return to the login screen. A Response.Redirect and a Server.Transfer did not work in the IsCurrentUserAdmin() method. How can I get the application to return the user to the login screen if the session token isUserAdmin is NULL?! Remember, the session token is set in the login.aspx page code behind
When the user closes the final tab of the Lightswitch application, the application opens a new tab and navigates past the login page and they are automatically logged in without processing the login process on the login.aspx page. This means that the session token isUserAdmin is NULL. This happens even if the user has not logged in before they closed the final tab of the application. This leads again to problem 1.
Thanks in advance!
If I understand your problem correctly, if, for whatever reason, isUserAdmin is set to NULL, you want to return the user to to the login screen.
In my application, I simply use a button that the user can click to log off. But the underlying method should work just the same in your case.
First create a new page called LogOff.aspx. The page itself, you can leave default generated code:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
For the code behind, you'll want something like this (please check this, I converted from my project which is in VB):
using System.Web.Security;
namespace LightSwitchApplication
{
public partial class LogOff : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect("default.htm");
}
}
}
This is my code in which I use a button. But if you take the section where the Dispatcher calls Navigate and place it in your IsCurrentUserAdmin() method, it should do the same trick (again, check the C#):
using Microsoft.LightSwitch.Threading;
using System.Windows.Browser;
partial void btnLogOff_Execute()
{
Dispatchers.Main.Invoke(() =>
{
HtmlPage.Window.Navigate(new Uri("LogOff.aspx", UriKind.Relative));
});
}
In my experience, there is a bit of a gotcha in Lightswitch. If you were to execute as is, you would probably receive the following:
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its
dependencies) could have been removed, had its name changed, or is
temporarily unavailable. Please review the following URL and make
sure that it is spelled correctly.
Requested URL: /LogOff.aspx
The fix is this:
First right click your project name in Solution Explorer and Unload Project. Once the project is unloaded, right click it and Edit project_name.lsproj. Ctrl+F for default.htm. You're looking for the section where it is proceeded by _BuildFile. Copy that section from _BuildFile to /_BuildFile, paste below that section and modify as follows.
<_BuildFile Include="Server/LogOff.aspx">
<SubFolder>
</SubFolder>
<PublishType>
</PublishType>
</_BuildFile>
Now right click and Reload your project. If you get errors when trying to build, try Build | Clean and build again. If you run the application in Debug, this code will just reload the page. But once you publish and subsequently cause isUserAdmin to be NULL the code should log you out and take you back to the log on screen.
References:
Original MSDN Forum Thread
My experience implementing it

What permissions do I need to grant to run RavenDB in Server mode?

I'm reading through Rob Ashton's excellent blog post on RavenDB:
http://codeofrob.com/archive/2010/05/09/ravendb-an-introduction.aspx
and I'm working through the code as I read. But when I try to add an index, I get a 401 error. Here's the code:
class Program
{
static void Main(string[] args)
{
using (var documentStore = new DocumentStore() { Url = "http://localhost:8080" })
{
documentStore.Initialise();
documentStore.DatabaseCommands.PutIndex(
"BasicEntityBySomeData",
new IndexDefinition<BasicEntity, BasicEntity>()
{
Map = docs => from doc in docs
where doc.SomeData != null
select new
{
SomeData = doc.SomeData
},
});
string entityId;
using (var documentSession = documentStore.OpenSession())
{
var entity = new BasicEntity()
{
SomeData = "Hello, World!",
SomeOtherData = "This is just another property",
};
documentSession.Store(entity);
documentSession.SaveChanges();
entityId = entity.Id;
var loadedEntity = documentSession.Load<BasicEntity>(entityId);
Console.WriteLine(loadedEntity.SomeData);
var docs = documentSession.Query<BasicEntity>("BasicEntityBySomeData")
.Where("SomeData:Hello~")
.WaitForNonStaleResults()
.ToArray();
docs.ToList().ForEach(doc => Console.WriteLine(doc.SomeData));
Console.Read();
}
}
}
It throws the 401 error when on the line that makes the PutIndex() call. Any ideas what permissions I need to apply? And where I need to apply them?
What do you mean by Server mode? Do you mean simply executing Raven.Server?
I've not had to do anything special client-side to get that to work, although I have had to run Raven.Server with elevated privileges because I'm not sure the code to ask for relevant permissions is quite working as intended. (Actually, I'll raise a query about that on the mailing list)
You shouldn't be getting a 401 error unless you've changed the configuration of Raven.Server.
If you're running the server, you can browse to it directly using the url specified in configuration (localhost:8080 by default) - make sure it's actually running and working as intended before continuing with troubleshooting

Creating an SPListItem in a WCF service deployed to SharePoint

i have the following method in a WCF service, that has been deployed to SharePoint using Shail Malik's guide:
[OperationContract]
public string AddItem(string itemTitle, Guid? idOfListToUse)
{
using (var portal = new SPSite(SPContext.Current.Site.Url, SPContext.Current.Site.SystemAccount.UserToken))
{
using (var web = portal.OpenWeb())
{
Guid listId;
web.AllowUnsafeUpdates = true;
if (idOfListToUse != null && idOfListToUse.Value != new Guid())
{
listId = idOfListToUse.Value;
}
else
{
try
{
listId = new Guid(web.Properties[PropertyBagKeys.TagsList]);
}
catch (Exception ex)
{
throw new MyException("No List Id for the tag list (default list) has been found!", ex);
}
}
var list = web.Lists[listId];
string title = "";
SPSecurity.RunWithElevatedPrivileges(delegate{
var newItem = list.Items.Add();
newItem["Title"] = itemTitle;
newItem.Update();
title = newItem.Title;
});
web.AllowUnsafeUpdates = false;
return title;
}
}
}
When the method gets called from Javascript (using Rick Strahl's excellent ServiceProxy.js) it fails and it does so on newItem.Update() because of ValidateFormDigest().
Here's the kicker though, when I step through the code it works! No exceptions at all!
Ok, found the answer (there's 2 even :-D)
First, the dirty one:
Set FormDigestValidatedProperty in the context:
HttpContext.Current.Items["FormDigestValidated"] = true;
Second, the slightly less dirty version (basically leaving the way open for XSS attacks, but this is an intranet anyway)
The answer
I don't think you can access 'list' as it was created outside the elevated code block.
http://blogs.pointbridge.com/Blogs/herzog_daniel/Pages/Post.aspx?_ID=8
I'm guessing when you are stepping though the entire process is in admin mode so all are elevated.
Colin, it's a really bad idea to try to access HttpContext (likewise SPContext) inside a WCF service. See here: MSDN: WCF Services and ASP.NET
From the article:
HttpContext: Current is always null
when accessed from within a WCF
service.
It's likely this is the cause of your problem.
EDIT: I notice that you're trying to use SPContext to get the url of the site collection. I didn't find a good solution to this either so I just send the url of the target site collection as a parameter to the service call. Not the most optimal solution but I couldn't think of a better way. Also, if you need to check authentication/identities, etc use ServiceSecurityContext.Current.