SPD2013 Workflow - how to check user is member of group

 

I want to describe a method that I use to check if a user is a member of a group.

 

Steps

  • Call a REST webservice
    • Reference MSDN for the correct API
    • Build a RequestURL and a basic RequestHeader
    • Figure out what the results mean
  • Wrap it up in a Workflow Custom Activity

 

API

MSDN (http://msdn.microsoft.com/en-us/library/office/dn268594(v=office.15).aspx - this needs to be a SharePoint Developer's home page) documents a few REST end points that I use for this.

http://msdn.microsoft.com/en-us/library/office/dn531432(v=office.15).aspx#bk_Group

Says you can get to a sharepoint group via:

  • http://<site url>/_api/web/sitegroups(<group id>)
  • http://<site url>/_api/web/sitegroups(<group name>)

The group also has a Users property that points to a Users Collection.

http://msdn.microsoft.com/en-us/library/office/dn531432(v=office.15).aspx#bk_UserCollection

This expands our example to:

  • http://<site url>/_api/web/sitegroups(<group id>)/users

For example:

 

The Users Collection does not have a method for testing if a user exists.  So I've taken the shortcut and basically brute force the service and just try to retrieve a user.  If you try to request a user that doesn't exist in the collection, it will just error, and I just catch that error.

 

SharePoint Designer workflow

 

image

 

Build Request Header

image

Both Accept and Content-Type needs to be "application/json;odata=verbose"

 

Build Request URL

image

Concatenate Current Site URL (which ends without a trailing /) and the earlier API.

Note my group name is 'john Members'

 

Call Web Service

image

 

Catch and process the result value.

image

 

The ResponseCode could be either OK or InternalServerError

Get a property from the returned Response variable "d/Title" would correspond to the Display Name of the user returned.  If the ResponseCode was Error, then there would be no value in the Response object.

 

 

Sandbox Custom Workflow Activity

 

In Visual Studio, these activities can be bundled into one single Activity that can be reused in SharePoint Designer.  I'll update this in a future blog post on Visual Studio.

 

 

Thoughts on checking nested group or AD group memberships

  • There are no way to check member with nested groups.  One possibility is to not think of it as membership, but think of it as whether the person has a certain permission.

    Does the current user have permission to do Contribute on the current Site. 
  • A more complicated thinking could be to create a list, kick out everyone except the group you are interested in, and check if the current user has permission to that list.

 

 

This Example in JavaScript

 

The more I work with SharePoint 2013 Workflows the closer parallels I see relating to a traditional programming language.  Here's the same function call in Javascript.

var promise = $.ajax({
        type: "GET",
        url: _spPageContextInfo.siteServerRelativeUrl + "/_api/web/sitegroups/getbyname('john Owners')/users/getbyid(" + _spPageContextInfo.userId + ")",
        headers: {
                "accept": "application/json;odata=verbose"
        },
        contentType: "application/json;odata=verbose",
        dataType: "json",
        cache: false,
        processData: true
});

promise.then(
        function (data, status) {

                if (status == "success" && data && data.d) {
                        var title = d.Title;

                }
                else {
                    // success, but no records - this can't really happen.
                }
        },
        function () {
                // not successful - usually not a member of that group
        }
);

TypeScript Definition file for SPServices v0.1

 

Here's something I have wanted to write for a long time, both as Thank-You to Marc Anderson's work on SPServices, as well as for personal learning of the TypeScript language.

 

Adding TypeScript Definition reference:

image

 

$().sp (intellisense)

image

 

$().SPServices.defaults.cacheXML

image

 

$().SPServices.Version()

image

 

$().SPServices.SPCascadeDropdowns({ ... })

image

 

And one more.

$().SPServices({ ... })

image

 

Syntax Error Detection that TypeScript does so well. 

Except for the error message.

Repeat after me in a ROBOT way.  "String is missing apply from type Function".

What it means is I was expecting a function why did I find a string. 

And because Javascript can be surprisingly retarded, TypeScript tries to accommodate and wonders: perhaps this string is a function in disguise - so let me look for the apply method on this object.  NOPE.  It is not a function.   /s

 

image

 

 

Bonus.  TypeScript understands SPServices returns a JQueryXHR promise object.

image

 

Wait.  Where's all the other methods?

 

This is v0.1...

 

Download

https://static1.squarespace.com/static/5527bff2e4b0b430660b0d10/5527c30de4b030eeeef09715/5527c30ee4b030eeeef09d1c/1418221412217/jquery.SPServices.d.ts

Also, how do I get this into Marc's repo?

SharePoint Saturday Sydney 2014

November 29, 2014 was a great day for SharePoint Saturday.

I presented "Develop and Build Workflow Apps in SP2013. Wait, Workflow Apps?" - which is a session that covers lots of the new things you can build with SharePoint 2013 workflows using primarily Visual Studio 2013.  These work on premises and in Office 365.

 

The REST end points opens up SharePoint

 

I still think my transition along with exploring new activity and suddenly jumping into REST was tough.  I need to work on how to introduce that point.

The key point stands.  As a developer, or even a power user using Workflows - the REST API opens SharePoint completely to me.  I can use it to create lists, site columns or assign permission groups.  The trouble is that creating the JSON packet to talk to SharePoint end point is quite hard.  Which is why the need (and the ability) to package existing series of Actions into a reusable Custom Activity is a big deal.

 

Confusion over Workflow Custom Activity

 

There was actually a lot of confusion over the artefact "Custom Activity".  In 2010, these were sandbox or coded solutions.  In 2013, Custom Activity is completely declarative.  It is a way for you to save a series of workflow actions into a reusable piece of functionality that you can use over and over.  In my demo project I have about 10 custom activities.

 

Downloads

 

 

News Update

 

We announced at the beginning and the end of SharePoint Saturday 2014 that this would be the last SharePoint Saturday in Australia.  As Microsoft and Industry trends towards Office 365, we will move with that trend.

From 2015 onwards, Office 365 Saturday will return, bigger and with more coverage of Office and SharePoint Online, but also address your On Premises needs.

Hope you have a great holidays and see you soon in 2015!

Style recommendation for writing CAML in C#

 

I came across some sample CAML code a while back, and have been following their way of defining CAML in C#

Today a colleague commented on my code and I thought, hey, I should write this down.

 

Use string.concat

 

var query = new SPQuery()
{
    Query = string.Concat(
                                "<Where>",
                                    "<Eq>",
                                    "<FieldRef Name='Parent' LookupId='true'/>",
                                    "<Value Type='Integer'>", id, "</Value>",
                                    "</Eq>",
                                "</Where>",
                                "<OrderBy>",
                                    "<FieldRef Name='DueDate' Ascending='TRUE' />",
                                    "<FieldRef Name=’Priority’ Ascending='TRUE' />",
                                "</OrderBy>")
};
var items = list.GetItems(query);

 

Benefits

 

  • You don't have to bury the CAML in one crazy line or block of text.
  • I can arbitrarily indent my XML elements and the code doesn't see it at all. 
  • You almost always need to insert some sort of variables in the middle of your CAML.  This way you don't have to rely on fancy string.format, or consider type casting.
  • You don't have to remember to add "..." + "..." at the end of every line. 
  • Don't like a chunk of XML?  You can easily line comment or region comment.

 

Try it out and see if this changes your life as it has for me.

(if you have been doing this for years and have blogged or seen this documented before, let me know where.)

And I say to ASHX for SharePoint: make me a folder.

Sometimes, I do get to do some on-premises farm stuff.  Because deep in my soul, I'm a hardcore dev.  Muahaha.

Scenario

In InfoPath, we want to be able to:

  • Send people to a folder within a document library, for them to upload attachments. 
  • Each form has its own unique ID, say "1234".  The folder will be <site>/Attachments/1234/
  • The folder doesn't need to be created when it's not used.  That is, it would be great to create the folder ON DEMAND
  • Finally, InfoPath is quite dumb.  It only has a hyperlink.

 

Solution

  • Create a HTTP Handler that takes this URL:  <site/sitecollection>/_layouts/InfoPathHelper/InfoPathHandler.ashx?folder=<site>/attachments/<ID>
  • Create a folder on demand, and then respond via a HTTP Redirect.

 

Steps

 

image

  1. Add a ashx handler to your SharePoint solution.  CKSDev has great template for this.
  2. Add code to ProcessRequest
    public void ProcessRequest(HttpContext context)
    {
        if (!string.IsNullOrEmpty(context.Request.QueryString["folder"]))
        {
            CreateFolderAndRedirectResponse(context);
            return;
        }
    }

  3. Add a function to check for the folder, create it if we need it, and end with a redirect.

    private void CreateFolderAndRedirectResponse(HttpContext context)
    {
        // <site-collection>/_layouts/InfoPathHelper/InfoPathHandler.ashx?folder=<site>/

        var path = context.Request.QueryString["folder"];
        var server = new Uri(SPContext.Current.Web.Url);
        var url = string.Format("{0}://{1}{2}", server.Scheme, server.Authority, path);

        // elevate permission to create the folder.
        SPSecurity.RunWithElevatedPrivileges(() =>
        {
            try
            {
                using (var site = new SPSite(url))
                {
                    using (var web = site.OpenWeb())
                    {
                        SPFolder folder = web.GetFolder(path);
                        SPFolder f = folder;
                        List<SPFolder> folders = new List<SPFolder>();
                        SPDocumentLibrary library = folder.DocumentLibrary;
                        if (library == null)
                        {
                            return;
                        }
                        while(f.Url.ToLower() != library.RootFolder.Url.ToLower()){
                            if (f.Exists || string.IsNullOrEmpty(f.Url))
                            {
                                break;
                            }

                            folders.Add(f);

                            f = f.ParentFolder;
                            if (f == null)
                            {
                                // if this happens we're in trouble
                                return;
                            }
                        }


                        if (folders.Count > 0)
                        {
                            // we are in a GET request - need to allow unsafe updates
                            web.AllowUnsafeUpdates = true;
                            folders.Reverse();
                            foreach (SPFolder f1 in folders)
                            {
                                if (!f1.Exists)
                                {
                                    f1.ParentFolder.SubFolders.Add(f1.Name);
                                }
                            }
                            web.AllowUnsafeUpdates = false;
                        }
                    }
                }
            }
            catch (Exception ex)
            {

            }
        });

        context.Response.Redirect(path, true);
    }

  4. Wait what's all the strange looking nested folder stuff?  That's right, as a bonus, if you specify nested folder within the document library, the HTTP Handler will create those too!

    folder=/attachments/1234/1235/
  5. InfoPath is super simple, just add a Hyperlink to the URL

    image

    Link to Data Source:

    concat("/_layouts/InfoPathHelper/InfoPathHandler.ashx?attachments?folder=", my:ID)

 

See it running

 

image