{"id":308,"date":"2016-01-08T09:14:32","date_gmt":"2016-01-08T09:14:32","guid":{"rendered":"http:\/\/blogs.kent.ac.uk\/unseenit\/?p=308"},"modified":"2016-01-08T09:14:32","modified_gmt":"2016-01-08T09:14:32","slug":"group-populator-for-fim","status":"publish","type":"post","link":"https:\/\/blogs.kent.ac.uk\/unseenit\/group-populator-for-fim\/","title":{"rendered":"Group Populator for FIM"},"content":{"rendered":"<p>There are various commercial solutions for providing automatic group population within FIM (into other directories such as Active Directory) which look at arbitrary attributes in the Metaverse to invent and populate groups.<\/p>\n<p>For example creating a &#8220;All People with first name John&#8221; group which, given dynamic groups in your directory, would be implemented with an LDAP filter like <code>(givenName=John)<\/code>.<\/p>\n<p>The common method of implementing this is via a <em>reflection<\/em> MA which talks straight back onto an SQL VIEW of the FIM Metaverse which outputs groups and memberships. This is fine but the SQL MA isn&#8217;t known for its speed of Full Imports so the need for a Delta VIEW\u00a0is high when at scale!<\/p>\n<p>I&#8217;ve implemented aDelta VIEW by taking a snapshot of the base view and then my delta import job queries another view which compares the live metaverse data with this snapshot to generate a changelist. It&#8217;s not as quick as some commercial solutions but is a lot quicker than a Full Import \ud83d\ude42<\/p>\n<p>All of this is done in native SQL views\/jobs and (almost) code-less FIM Sync Service.<\/p>\n<h2>Group view<\/h2>\n<p>First create one or more group views, these could construct the group name from something in the metaverse or be pretty simple as below:<\/p>\n<div class=\"highlight sql\">\n<div class=\"codearea\" dir=\"ltr\" lang=\"en\">\n<pre id=\"CA-cb02a9ca76a84ee8ca1bd22530fbe6ca968abb1e\"><span class=\"line\"><span class=\"ResWord\">CREATE<\/span> <span class=\"ResWord\">VIEW<\/span> [<span class=\"ID\">dbo<\/span>].[<span class=\"ID\">gp_example<\/span>] <\/span>\r\n<span class=\"line\"><span id=\"line-2-1\" class=\"anchor\"><\/span><span class=\"ResWord\">AS<\/span> <\/span>\r\n<span class=\"line\"><span id=\"line-3-1\" class=\"anchor\"><\/span>  <span class=\"ResWord\">SELECT<\/span> <span class=\"ID\">object_id<\/span>    <span class=\"ResWord\">AS<\/span> <span class=\"ID\">member<\/span>, <\/span>\r\n<span class=\"line\"><span id=\"line-4-1\" class=\"anchor\"><\/span>         <span class=\"String\">'All People with first name John'<\/span> <span class=\"ResWord\">AS<\/span> [<span class=\"ResWord\">group<\/span>] <\/span>\r\n<span class=\"line\"><span id=\"line-5-1\" class=\"anchor\"><\/span>  <span class=\"ResWord\">FROM<\/span>   <span class=\"ID\">fimsynchronizationservice<\/span>.<span class=\"ID\">dbo<\/span>.<span class=\"ID\">mms_metaverse<\/span> <span class=\"ResWord\">WITH<\/span> (<span class=\"ID\">nolock<\/span>) <\/span>\r\n<span class=\"line\"><span id=\"line-6-1\" class=\"anchor\"><\/span>  <span class=\"ResWord\">WHERE<\/span>  ( <span class=\"ID\">givenName<\/span> = <span class=\"String\">'John'<\/span> );<\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<pre><\/pre>\n<p>I have done it this way to keep different types of groups more manageable rather than having one EPIC view.<\/p>\n<p>Please remember to keep the <code>WITH (nolock)<\/code> in place as it avoids any SQL layer locking on the metaverse which can cause deadlocks within FIM if missed.<\/p>\n<h2>Master SQL\u00a0view<\/h2>\n<p>Once you have some group views, knit them together into a master and a multivalue view using UNION:<\/p>\n<h3>Master<\/h3>\n<pre>CREATE VIEW [dbo].[gp_master] ([object] , [type])\r\nAS<\/pre>\n<div class=\"highlight sql\">\n<div class=\"codearea\" dir=\"ltr\" lang=\"en\">\n<pre id=\"CA-09686d9adaa0beba86bc39eaeee65b1b47a66e55\"><span class=\"line\"><span class=\"ResWord\">SELECT<\/span> <span class=\"ResWord\">DISTINCT<\/span> [<span class=\"ResWord\">group<\/span>] <span class=\"ResWord\">AS<\/span> <span class=\"ResWord\">object<\/span>, <\/span>\r\n<span class=\"line\"><span id=\"line-3-2\" class=\"anchor\"><\/span>                  <span class=\"String\">'group'<\/span> <span class=\"ResWord\">AS<\/span> [<span class=\"ResWord\">type<\/span>] <\/span>\r\n<span class=\"line\"><span id=\"line-4-2\" class=\"anchor\"><\/span>  <span class=\"ResWord\">FROM<\/span>   <span class=\"ID\">dbo<\/span>.<span class=\"ID\">gp_example<\/span><\/span>\r\n<span class=\"line\"><span id=\"line-5-2\" class=\"anchor\"><\/span>  <span class=\"ResWord\">UNION<\/span> <\/span>\r\n<span class=\"line\"><span id=\"line-6-2\" class=\"anchor\"><\/span>  <span class=\"ResWord\">SELECT<\/span> <span class=\"ID\">Concat<\/span>(<span class=\"String\">'{'<\/span>, <span class=\"ResWord\">object<\/span>, <span class=\"String\">'}'<\/span>) <span class=\"ResWord\">AS<\/span> <span class=\"ResWord\">object<\/span>, <\/span>\r\n<span class=\"line\"><span id=\"line-7-1\" class=\"anchor\"><\/span>         <span class=\"ResWord\">type<\/span> <\/span>\r\n<span class=\"line\"><span id=\"line-8-1\" class=\"anchor\"><\/span>  <span class=\"ResWord\">FROM<\/span>   (<span class=\"ResWord\">SELECT<\/span> <span class=\"ResWord\">DISTINCT<\/span> [<span class=\"ID\">member<\/span>] <span class=\"ResWord\">AS<\/span> <span class=\"ResWord\">object<\/span>, <\/span>\r\n<span class=\"line\"><span id=\"line-9-1\" class=\"anchor\"><\/span>                          <span class=\"String\">'member'<\/span> <span class=\"ResWord\">AS<\/span> [<span class=\"ResWord\">type<\/span>] <\/span>\r\n<span class=\"line\"><span id=\"line-10-1\" class=\"anchor\"><\/span>          <span class=\"ResWord\">FROM<\/span>   <span class=\"ID\">dbo<\/span>.<span class=\"ID\">gp_example<\/span>) <span class=\"ID\">t<\/span><\/span>\r\n<\/pre>\n<\/div>\n<\/div>\n<pre><span id=\"line-40\" class=\"anchor\"><\/span>UNION\r\n...<\/pre>\n<p>This should output rows of two types: &#8220;member&#8221; which as a guid in the object\u00a0column\u00a0and &#8220;group&#8221; which has the real name of the group in &#8220;object&#8221; column.<\/p>\n<h3>Multivalue<\/h3>\n<pre>CREATE VIEW [dbo].[gp_master_mv] \r\nAS \r\n SELECT [group] AS object, \r\n 'member' AS attribute, \r\n member AS value \r\n FROM dbo.gp_example\r\n UNION\r\n ...<\/pre>\n<p>This view should output rows of the form &lt;group&gt;, &#8220;member&#8221;, &lt;objectid of member&gt;.<\/p>\n<h2>Delta snapshots<\/h2>\n<p>Next is the spapshot tables against which we&#8217;ll compare live data. These need bootstrapping before we use them. I&#8217;ve found the easiest way to do this is to get SQL Server to create them from your views:<\/p>\n<pre id=\"CA-00ac4991b3c8e299da9319c805420d843523ed17\">SELECT * INTO dbo.gp_master_copy FROM dbo.gp_master;\r\nSELECT * INTO dbo.gp_master_mv_copy FROM dbo.gp_master_mv;\r\n<\/pre>\n<p>This will create a\u00a0static copy of your master and multivalue tables.<\/p>\n<p>Using these snapshots, you then need your Delta VIEW:<\/p>\n<pre>CREATE VIEW [dbo].[gp_delta] \r\nAS \r\n  SELECT mv.object, \r\n         'group'  AS [type], \r\n         'Modify' AS change_type \r\n  FROM   dbo.gp_master_mv mv \r\n         LEFT OUTER JOIN dbo.gp_master_mv_copy mv_copy \r\n                      ON mv.object = mv_copy.object \r\n                         AND mv.attribute = mv_copy.attribute \r\n                         AND mv.value = mv_copy.value \r\n  WHERE  mv_copy.object IS NULL \r\n  UNION \r\n  SELECT mv_copy.object, \r\n         'group'  AS [type], \r\n         'Modify' AS CHANGETYPE \r\n  FROM   dbo.gp_master_mv_copy mv_copy \r\n         LEFT OUTER JOIN dbo.gp_master_mv mv \r\n                      ON mv.object = mv_copy.object \r\n                         AND mv.attribute = mv_copy.attribute \r\n                         AND mv.value = mv_copy.value \r\n  WHERE  mv.object IS NULL \r\n  UNION \r\n  SELECT m.object, \r\n         'member' AS [type], \r\n         'Add'    AS CHANGETYPE \r\n  FROM   dbo.gp_master m \r\n         LEFT OUTER JOIN dbo.gp_master_copy m_copy \r\n                      ON m.object = m_copy.object \r\n  WHERE  m.type = 'member' \r\n         AND m_copy.object IS NULL \r\n  UNION \r\n  SELECT m.object, \r\n         'member' AS [type], \r\n         'Delete' AS CHANGETYPE \r\n  FROM   dbo.gp_master m \r\n         LEFT OUTER JOIN dbo.gp_master_copy m_copy \r\n                      ON m.object = m_copy.object \r\n  WHERE  m.type = 'member' \r\n         AND m.object IS NULL \r\n\r\n<\/pre>\n<p>This view will output any differences between the live master\/multivalue views and the stored copy in a way that FIM expects (tagged with Add, Modify or Delete).<\/p>\n<h2>Making it all work<\/h2>\n<p>Now you have all your SQL layer stuff working, you need to create a new FIM MA to retrieve the data.<\/p>\n<ol>\n<li>Connect to database:\n<ol>\n<li>Table\/View: gp_master<\/li>\n<li>Delta VIew: gp_delta<\/li>\n<li>Multivalue Table: gp_master_mv<\/li>\n<\/ol>\n<\/li>\n<li>Configure columns:\n<ol>\n<li>Set anchor to object<\/li>\n<li>Configure Delta\n<ol>\n<li>Change type attribute: change_type<\/li>\n<li>Modify=Modify<\/li>\n<li>Add=Add<\/li>\n<li>Delete=Delete<\/li>\n<\/ol>\n<\/li>\n<li>Multivalue\n<ol>\n<li>Attribute name column: attribute<\/li>\n<li>String attribute column: value<\/li>\n<li>Configure attributes -&gt; New\n<ol>\n<li>Name: member<\/li>\n<li>Type: String<\/li>\n<li>Reference: yes<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<\/li>\n<li>Object type\n<ol>\n<li>Select Object type column = type<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<\/li>\n<li>Configure Join and Projection rules (I&#8217;ve chosen the following, amend as you need!)\n<ol>\n<li>Group: Join on object=cn OR Project<\/li>\n<li>Member: Join on object=&lt;object-id&gt; (do not project)<\/li>\n<\/ol>\n<\/li>\n<li>Attribute flow (all of these are Import Attribute Flows, data source to metaverse, direct group -&gt; group)\n<ol>\n<li>member -&gt; member<\/li>\n<li>object -&gt; cn<\/li>\n<li>object -&gt; sAMAccountName<\/li>\n<\/ol>\n<\/li>\n<li>Deprovisioning\n<ol>\n<li>Make them disconnectors<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n<p>Run a Full Import &amp; Delta Sync (in my environment of ~700 groups, 100,000 users and 170,000 memberships this took <strong>hours<\/strong>).<\/p>\n<p>As for then using the Delta view, in order to not miss things, I&#8217;ve worked this by having an SQL script\u00a0to re-do the snapshots which is called after my Delta Import has finished but before any other MAs run:<\/p>\n<pre>use MyDB;\r\nDELETE FROM dbo.gp_master_copy WHERE 1=1;\r\nDELETE FROM dbo.gp_master_mv_copy WHERE 1=1;\r\nINSERT INTO dbo.gp_master_copy SELECT * FROM dbo.gp_master;\r\nINSERT INTO dbo.gp_master_mv_copy SELECT * FROM dbo.gp_master_mv;<\/pre>\n<p>I then invoke this from the batch script which calls my MA Run Profiles like this:<\/p>\n<pre>call runma.cmd DI-DS GP2\r\nsqlcmd -i gp_snapshot.sql<\/pre>\n<p>This Delta Import &amp; Delta Sync run then takes about 90 seconds \ud83d\ude42<\/p>\n<h2>What next<\/h2>\n<p>Next you&#8217;ll need some provisioning code to create these groups in your chosen directory (Active Directory?) and flow &#8220;member&#8221; into those group objects.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are various commercial solutions for providing automatic group population within FIM (into other directories such as Active Directory) which look at arbitrary attributes in &hellip; <a href=\"https:\/\/blogs.kent.ac.uk\/unseenit\/group-populator-for-fim\/\">Read&nbsp;more<\/a><\/p>\n","protected":false},"author":13,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[28943,28936,862],"tags":[],"_links":{"self":[{"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/posts\/308"}],"collection":[{"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/comments?post=308"}],"version-history":[{"count":2,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/posts\/308\/revisions"}],"predecessor-version":[{"id":311,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/posts\/308\/revisions\/311"}],"wp:attachment":[{"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/media?parent=308"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/categories?post=308"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.kent.ac.uk\/unseenit\/wp-json\/wp\/v2\/tags?post=308"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}