private void GetExternalUsers(Office365Tenant tenant)
        {
            GetExternalUsersResults results = null;

            if (!string.IsNullOrEmpty(SiteUrl))
            {
                results = tenant.GetExternalUsersForSite(SiteUrl, Position, PageSize, Filter, SortOrder);
            }
            else
            {
                results = tenant.GetExternalUsers(Position, PageSize, Filter, SortOrder);
            }
            ClientContext.Load(results, r => r.TotalUserCount, r => r.UserCollectionPosition, r => r.ExternalUserCollection.Include(u => u.DisplayName, u => u.InvitedAs, u => u.UniqueId, u => u.AcceptedAs, u => u.WhenCreated, u => u.InvitedBy));
            ClientContext.ExecuteQueryRetry();
            foreach (var externalUser in results.ExternalUserCollection)
            {
                if (!ShowOnlyUsersWithAcceptingAccountNotMatchInvitedAccount)
                {
                    WriteObject(externalUser);
                }
                else if (!string.Equals(externalUser.InvitedAs, externalUser.AcceptedAs, System.StringComparison.OrdinalIgnoreCase))
                {
                    WriteObject(externalUser);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Returns a list all external users for a given site that have at least the viewpages permission
        /// </summary>
        /// <param name="web">Tenant administration web</param>
        /// <param name="siteUrl">Url of the site fetch the external users for</param>
        /// <returns>A list of <see cref="OfficeDevPnP.Core.Entities.ExternalUserEntity"/> objects</returns>
        public static List <ExternalUserEntity> GetExternalUsersForSiteTenant(this Web web, Uri siteUrl)
        {
            if (siteUrl == null)
            {
                throw new ArgumentNullException("siteUrl");
            }

            Tenant          tenantAdmin = new Tenant(web.Context);
            Office365Tenant tenant      = new Office365Tenant(web.Context);
            Site            site        = tenantAdmin.GetSiteByUrl(siteUrl.OriginalString);

            web = site.RootWeb;

            List <ExternalUserEntity> externalUsers = new List <ExternalUserEntity>();
            int pageSize = 50;
            int position = 0;
            GetExternalUsersResults results = null;

            while (true)
            {
                results = tenant.GetExternalUsersForSite(siteUrl.OriginalString, position, pageSize, string.Empty, SortOrder.Ascending);
                web.Context.Load(results, r => r.UserCollectionPosition, r => r.TotalUserCount, r => r.ExternalUserCollection);
                web.Context.ExecuteQuery();

                foreach (var externalUser in results.ExternalUserCollection)
                {
                    User user = web.SiteUsers.GetByEmail(externalUser.AcceptedAs);
                    web.Context.Load(user);
                    web.Context.ExecuteQuery();

                    var permission = web.GetUserEffectivePermissions(user.LoginName);
                    web.Context.ExecuteQuery();
                    var doesUserHavePermission = permission.Value.Has(PermissionKind.ViewPages);
                    if (doesUserHavePermission)
                    {
                        externalUsers.Add(new ExternalUserEntity()
                        {
                            DisplayName = externalUser.DisplayName,
                            AcceptedAs  = externalUser.AcceptedAs,
                            InvitedAs   = externalUser.InvitedAs,
                            InvitedBy   = externalUser.InvitedBy,
                            UniqueId    = externalUser.UniqueId,
                            WhenCreated = externalUser.WhenCreated
                        });
                    }
                }

                position = results.UserCollectionPosition;

                if (position == -1 || position == results.TotalUserCount)
                {
                    break;
                }
            }

            return(externalUsers);
        }
        /// <summary>
        /// Query the Tenant UPS based on Site Collection
        /// </summary>
        /// <param name="siteUrl"></param>
        /// <param name="invitedAs"></param>
        /// <returns></returns>
        public static List <SPExternalUserEntity> CheckExternalUserForSite(this ClientContext adminContext, ITraceLogger logger, string siteUrl, string invitedAs = "")
        {
            if (siteUrl == null)
            {
                throw new ArgumentNullException("siteUrl");
            }


            var externalUsers = new List <SPExternalUserEntity>();
            int pageSize      = 50;
            int position      = 0;
            GetExternalUsersResults results = null;

            var officeTenantContext = new Office365Tenant(adminContext);

            while (true)
            {
                logger.LogInformation($"Checking External User with {invitedAs} at start {position} and page size {pageSize}");

                results = officeTenantContext.GetExternalUsersForSite(siteUrl, position, pageSize, invitedAs, SortOrder.Ascending);
                adminContext.Load(results, r => r.UserCollectionPosition, r => r.TotalUserCount, r => r.ExternalUserCollection);
                adminContext.ExecuteQueryRetry();

                foreach (ExternalUser externalUser in results.ExternalUserCollection)
                {
                    externalUsers.Add(new SPExternalUserEntity()
                    {
                        AcceptedAs  = externalUser.AcceptedAs,
                        DisplayName = externalUser.DisplayName,
                        InvitedAs   = externalUser.InvitedAs,
                        InvitedBy   = externalUser.InvitedBy,
                        UniqueId    = externalUser.UniqueId,
                        UserId      = externalUser.UserId,
                        WhenCreated = externalUser.WhenCreated
                    });
                }

                position = results.UserCollectionPosition;

                if (position == -1 || position == results.TotalUserCount)
                {
                    break;
                }
            }

            return(externalUsers);
        }
Example #4
0
        /// <summary>
        /// Returns a list all external users for a given site that have at least the viewpages permission
        /// </summary>
        /// <param name="web">Tenant administration web</param>
        /// <param name="siteUrl">Url of the site fetch the external users for</param>
        /// <returns>A list of <see cref="OfficeDevPnP.Core.Entities.ExternalUserEntity"/> objects</returns>
        public static List<ExternalUserEntity> GetExternalUsersForSiteTenant(this Web web, Uri siteUrl)
        {
            if (siteUrl == null)
                throw new ArgumentNullException("siteUrl");

            Tenant tenantAdmin = new Tenant(web.Context);
            Office365Tenant tenant = new Office365Tenant(web.Context);
            Site site = tenantAdmin.GetSiteByUrl(siteUrl.OriginalString);
            web = site.RootWeb;

            List<ExternalUserEntity> externalUsers = new List<ExternalUserEntity>();
            int pageSize = 50;
            int position = 0;
            GetExternalUsersResults results = null;

            while (true)
            {
                results = tenant.GetExternalUsersForSite(siteUrl.OriginalString, position, pageSize, string.Empty, SortOrder.Ascending);
                web.Context.Load(results, r => r.UserCollectionPosition, r => r.TotalUserCount, r => r.ExternalUserCollection);
                web.Context.ExecuteQuery();

                foreach (var externalUser in results.ExternalUserCollection)
                {

                    User user = web.SiteUsers.GetByEmail(externalUser.AcceptedAs);
                    web.Context.Load(user);
                    web.Context.ExecuteQuery();

                    var permission = web.GetUserEffectivePermissions(user.LoginName);
                    web.Context.ExecuteQuery();
                    var doesUserHavePermission = permission.Value.Has(PermissionKind.ViewPages);
                    if (doesUserHavePermission)
                    {
                        externalUsers.Add(new ExternalUserEntity()
                        {
                            DisplayName = externalUser.DisplayName,
                            AcceptedAs = externalUser.AcceptedAs,
                            InvitedAs = externalUser.InvitedAs,
                            InvitedBy = externalUser.InvitedBy,
                            UniqueId = externalUser.UniqueId,
                            WhenCreated = externalUser.WhenCreated
                        });
                    }

                }

                position = results.UserCollectionPosition;

                if (position == -1 || position == results.TotalUserCount)
                {
                    break;
                }
            }

            return externalUsers;
        }
Example #5
0
        static void Main(string[] args)
        {
            //get governance variables such as warning duration and cutoff duration
            int    warningDuration = Convert.ToInt32(ConfigurationManager.AppSettings["WarningDuration"]);
            int    cutoffDuration  = Convert.ToInt32(ConfigurationManager.AppSettings["CutoffDuration"]);
            string tenantName      = ConfigurationManager.AppSettings["TenantName"];
            string tenantUpnDomain = ConfigurationManager.AppSettings["TenantUpnDomain"];
            Uri    tenantAdminUri  = new Uri(String.Format("https://{0}-admin.sharepoint.com", tenantName));

            string webUrl = "";

            #if DEBUG
            webUrl = "http://localhost:25440/";
            #else
            webUrl = "https://sposharing.azurewebsites.net/";
            #endif

            foreach (var siteUrl in sites)
            {
                //initialize a process date for this site and clean up to match SQL percision
                DateTime processDate     = DateTime.Now;
                string   stringTicks     = processDate.Ticks.ToString();
                int      adjustmentTicks = Convert.ToInt32(stringTicks.Substring(stringTicks.Length - 5));
                processDate = processDate.Subtract(TimeSpan.FromTicks(adjustmentTicks));

                //use O365 Tenant Administration to get all the external sharing details for this site
                List <ExternalShareDetails> shares = new List <ExternalShareDetails>();
                string adminRealm = TokenHelper.GetRealmFromTargetUrl(tenantAdminUri);
                var    adminToken = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, tenantAdminUri.Authority, adminRealm).AccessToken;
                using (var clientContext = TokenHelper.GetClientContextWithAccessToken(tenantAdminUri.ToString(), adminToken))
                {
                    //load the tenant
                    var tenant = new Office365Tenant(clientContext);
                    clientContext.Load(tenant);
                    clientContext.ExecuteQuery();

                    //initalize varables to going through the paged results
                    int  position = 0;
                    bool hasMore  = true;
                    while (hasMore)
                    {
                        //get external users 50 at a time (this is the limit and why we are paging)
                        var externalUsers = tenant.GetExternalUsersForSite(siteUrl, position, 50, String.Empty, SortOrder.Descending);
                        clientContext.Load(externalUsers, i => i.TotalUserCount);
                        clientContext.Load(externalUsers, i => i.ExternalUserCollection);
                        clientContext.ExecuteQuery();

                        //convert each external user to our own entity
                        foreach (var extUser in externalUsers.ExternalUserCollection)
                        {
                            position++;
                            shares.Add(new ExternalShareDetails()
                            {
                                AcceptedAs  = extUser.AcceptedAs.ToLower(),
                                DisplayName = extUser.DisplayName,
                                InvitedAs   = extUser.InvitedAs.ToLower(),
                                InvitedBy   = (String.IsNullOrEmpty(extUser.InvitedBy)) ? null : extUser.InvitedBy.ToLower(),
                                UserId      = extUser.UserId,
                                WhenCreated = extUser.WhenCreated
                            });
                        }

                        //determine if we have more pages to process
                        hasMore = (externalUsers.TotalUserCount > position);
                    }
                }

                //get an AppOnly accessToken and clientContext for the site collection
                Uri    siteUri     = new Uri(siteUrl);
                string realm       = TokenHelper.GetRealmFromTargetUrl(siteUri);
                string accessToken = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, siteUri.Authority, realm).AccessToken;
                using (var clientContext = TokenHelper.GetClientContextWithAccessToken(siteUri.ToString(), accessToken))
                {
                    //first we need to load the site to determine if external sharing is enabled (Site.ShareByEmailEnabled)
                    var site      = clientContext.Site;
                    var siteOwner = clientContext.Site.Owner;
                    clientContext.Load(site);
                    clientContext.Load(siteOwner); //include the site owner in case the share "InvitedBy" is null...we will send them email instead
                    clientContext.ExecuteQuery();

                    //validate that the site has sharing turned on
                    if (site.ShareByEmailEnabled)
                    {
                        //process all of the shares
                        foreach (var externalShare in shares)
                        {
                            //look for an existing record in the database
                            using (ExternalSharingDataEntities entities = new ExternalSharingDataEntities())
                            {
                                var shareRecord = entities.ExternalShares.FirstOrDefault(i => i.LoginName.Equals(externalShare.AcceptedAs));
                                if (shareRecord != null)
                                {
                                    //Update LastProcessedDate column of the record with the processDate
                                    shareRecord.LastProcessedDate = processDate;
                                    entities.SaveChanges();
                                }
                                else
                                {
                                    //get the original share date
                                    var details = getREST(accessToken, String.Format("{0}/_api/Web/SiteUserInfoList/Items({1})/FieldValuesAsText", siteUrl, externalShare.UserId));
                                    externalShare.WhenCreated = Convert.ToDateTime(details.Descendants(ns + "Created").FirstOrDefault().Value);
                                    shareRecord = new ExternalShare()
                                    {
                                        UniqueIdentifier   = Guid.NewGuid(),
                                        SiteCollectionUrl  = siteUrl.ToLower(),
                                        LoginName          = externalShare.AcceptedAs,
                                        UserId             = externalShare.UserId,
                                        InvitedBy          = (String.IsNullOrEmpty(externalShare.InvitedBy)) ? siteOwner.Email : externalShare.InvitedBy,
                                        OriginalSharedDate = externalShare.WhenCreated,
                                        LastProcessedDate  = processDate
                                    };
                                    entities.ExternalShares.Add(shareRecord);
                                    entities.SaveChanges();
                                }

                                //check if the record falls inside the warnings
                                double daysActive = processDate.Subtract(shareRecord.OriginalSharedDate).TotalDays;
                                if (shareRecord.RefreshSharedDate != null)
                                {
                                    daysActive = processDate.Subtract((DateTime)shareRecord.RefreshSharedDate).TotalDays;
                                }

                                //check for cutoff
                                if (daysActive > cutoffDuration)
                                {
                                    //remove the SPUser from the site
                                    clientContext.Web.SiteUsers.RemoveById(externalShare.UserId);
                                    clientContext.ExecuteQuery();

                                    //delete the record
                                    entities.ExternalShares.Remove(shareRecord);
                                    entities.SaveChanges();
                                }
                                else if (daysActive > warningDuration)
                                {
                                    int expiresIn = Convert.ToInt32(cutoffDuration - daysActive);
                                    //send email to InvitedBy (which will be site collection owner when null)
                                    EmailProperties email = new EmailProperties();
                                    email.To = new List <String>()
                                    {
                                        shareRecord.InvitedBy
                                    };
                                    email.Subject = String.Format("Action Required: External sharing with {0} about to expire", externalShare.AcceptedAs);
                                    email.Body    = String.Format("<html><body><p>You are receiving this message because you are the site administrator of <a href='{0}'>{0}</a> OR you shared it with {1}. The external access for this user is set to expire in {2} days. Use the link below to view additional details and perform actions to revoke OR extend access for another {3} days. If you do not act on this notice, the external access for this user to terminate in {2} days.</p><ul><li><a href='{4}Details/{5}'>View Details</a></li><li><a href='{4}Extend/{5}'>Extend {3} Days</a></li><li><a href='{4}Revoke/{5}'>Revoke Access</a></li></ul></body></html>", siteUrl, externalShare.AcceptedAs, expiresIn.ToString(), cutoffDuration.ToString(), webUrl, shareRecord.UniqueIdentifier);
                                    Utility.SendEmail(clientContext, email);
                                    clientContext.ExecuteQuery();
                                }
                            }
                        }
                    }
                }

                //delete all database records for this site that have an older processDate...these should represent external users deleted by manually
                using (ExternalSharingDataEntities entities = new ExternalSharingDataEntities())
                {
                    var cleanUpRecords = entities.ExternalShares.Where(i => i.SiteCollectionUrl.Equals(siteUrl.ToLower()) && i.LastProcessedDate < processDate);
                    foreach (var record in cleanUpRecords)
                    {
                        entities.ExternalShares.Remove(record);
                        entities.SaveChanges();
                    }
                }
            }
        }
Example #6
0
        static void Main(string[] args)
        {
            //get governance variables such as warning duration and cutoff duration
            int warningDuration = Convert.ToInt32(ConfigurationManager.AppSettings["WarningDuration"]);
            int cutoffDuration = Convert.ToInt32(ConfigurationManager.AppSettings["CutoffDuration"]);
            string tenantName = ConfigurationManager.AppSettings["TenantName"];
            string tenantUpnDomain = ConfigurationManager.AppSettings["TenantUpnDomain"];
            Uri tenantAdminUri = new Uri(String.Format("https://{0}-admin.sharepoint.com", tenantName));

            string webUrl = "";
            #if DEBUG
            webUrl = "http://localhost:25440/";
            #else
            webUrl = "https://sposharing.azurewebsites.net/";
            #endif

            foreach (var siteUrl in sites)
            {
                //initialize a process date for this site and clean up to match SQL percision
                DateTime processDate = DateTime.Now;
                string stringTicks = processDate.Ticks.ToString();
                int adjustmentTicks = Convert.ToInt32(stringTicks.Substring(stringTicks.Length - 5));
                processDate = processDate.Subtract(TimeSpan.FromTicks(adjustmentTicks));

                //use O365 Tenant Administration to get all the external sharing details for this site
                List<ExternalShareDetails> shares = new List<ExternalShareDetails>();
                string adminRealm = TokenHelper.GetRealmFromTargetUrl(tenantAdminUri);
                var adminToken = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, tenantAdminUri.Authority, adminRealm).AccessToken;
                using (var clientContext = TokenHelper.GetClientContextWithAccessToken(tenantAdminUri.ToString(), adminToken))
                {
                    //load the tenant
                    var tenant = new Office365Tenant(clientContext);
                    clientContext.Load(tenant);
                    clientContext.ExecuteQuery();

                    //initalize varables to going through the paged results
                    int position = 0;
                    bool hasMore = true;
                    while (hasMore)
                    {
                        //get external users 50 at a time (this is the limit and why we are paging)
                        var externalUsers = tenant.GetExternalUsersForSite(siteUrl, position, 50, String.Empty, SortOrder.Descending);
                        clientContext.Load(externalUsers, i => i.TotalUserCount);
                        clientContext.Load(externalUsers, i => i.ExternalUserCollection);
                        clientContext.ExecuteQuery();

                        //convert each external user to our own entity
                        foreach (var extUser in externalUsers.ExternalUserCollection)
                        {
                            position++;
                            shares.Add(new ExternalShareDetails()
                            {
                                AcceptedAs = extUser.AcceptedAs.ToLower(),
                                DisplayName = extUser.DisplayName,
                                InvitedAs = extUser.InvitedAs.ToLower(),
                                InvitedBy = (String.IsNullOrEmpty(extUser.InvitedBy)) ? null : extUser.InvitedBy.ToLower(),
                                UserId = extUser.UserId,
                                WhenCreated = extUser.WhenCreated
                            });
                        }
                        
                        //determine if we have more pages to process
                        hasMore = (externalUsers.TotalUserCount > position);
                    }
                }

                //get an AppOnly accessToken and clientContext for the site collection
                Uri siteUri = new Uri(siteUrl);
                string realm = TokenHelper.GetRealmFromTargetUrl(siteUri);
                string accessToken = TokenHelper.GetAppOnlyAccessToken(TokenHelper.SharePointPrincipal, siteUri.Authority, realm).AccessToken;
                using (var clientContext = TokenHelper.GetClientContextWithAccessToken(siteUri.ToString(), accessToken))
                {
                    //first we need to load the site to determine if external sharing is enabled (Site.ShareByEmailEnabled)
                    var site = clientContext.Site;
                    var siteOwner = clientContext.Site.Owner;
                    clientContext.Load(site);
                    clientContext.Load(siteOwner); //include the site owner in case the share "InvitedBy" is null...we will send them email instead
                    clientContext.ExecuteQuery();

                    //validate that the site has sharing turned on
                    if (site.ShareByEmailEnabled)
                    {
                        //process all of the shares
                        foreach (var externalShare in shares)
                        {
                            //look for an existing record in the database
                            using (ExternalSharingDataEntities entities = new ExternalSharingDataEntities())
                            {
                                var shareRecord = entities.ExternalShares.FirstOrDefault(i => i.LoginName.Equals(externalShare.AcceptedAs));
                                if (shareRecord != null)
                                {
                                    //Update LastProcessedDate column of the record with the processDate
                                    shareRecord.LastProcessedDate = processDate;
                                    entities.SaveChanges();
                                }
                                else
                                {
                                    //get the original share date
                                    var details = getREST(accessToken, String.Format("{0}/_api/Web/SiteUserInfoList/Items({1})/FieldValuesAsText", siteUrl, externalShare.UserId));
                                    externalShare.WhenCreated = Convert.ToDateTime(details.Descendants(ns + "Created").FirstOrDefault().Value);
                                    shareRecord = new ExternalShare()
                                    {
                                        UniqueIdentifier = Guid.NewGuid(),
                                        SiteCollectionUrl = siteUrl.ToLower(),
                                        LoginName = externalShare.AcceptedAs,
                                        UserId = externalShare.UserId,
                                        InvitedBy = (String.IsNullOrEmpty(externalShare.InvitedBy)) ? siteOwner.Email : externalShare.InvitedBy,
                                        OriginalSharedDate = externalShare.WhenCreated,
                                        LastProcessedDate = processDate
                                    };
                                    entities.ExternalShares.Add(shareRecord);
                                    entities.SaveChanges();
                                }

                                //check if the record falls inside the warnings
                                double daysActive = processDate.Subtract(shareRecord.OriginalSharedDate).TotalDays;
                                if (shareRecord.RefreshSharedDate != null)
                                    daysActive = processDate.Subtract((DateTime)shareRecord.RefreshSharedDate).TotalDays;

                                //check for cutoff
                                if (daysActive > cutoffDuration)
                                {
                                    //remove the SPUser from the site
                                    clientContext.Web.SiteUsers.RemoveById(externalShare.UserId);
                                    clientContext.ExecuteQuery();

                                    //delete the record
                                    entities.ExternalShares.Remove(shareRecord);
                                    entities.SaveChanges();
                                }
                                else if (daysActive > warningDuration)
                                {
                                    int expiresIn = Convert.ToInt32(cutoffDuration - daysActive);
                                    //send email to InvitedBy (which will be site collection owner when null)
                                    EmailProperties email = new EmailProperties();
                                    email.To = new List<String>() { shareRecord.InvitedBy };
                                    email.Subject = String.Format("Action Required: External sharing with {0} about to expire", externalShare.AcceptedAs);
                                    email.Body = String.Format("<html><body><p>You are receiving this message because you are the site administrator of <a href='{0}'>{0}</a> OR you shared it with {1}. The external access for this user is set to expire in {2} days. Use the link below to view additional details and perform actions to revoke OR extend access for another {3} days. If you do not act on this notice, the external access for this user to terminate in {2} days.</p><ul><li><a href='{4}Details/{5}'>View Details</a></li><li><a href='{4}Extend/{5}'>Extend {3} Days</a></li><li><a href='{4}Revoke/{5}'>Revoke Access</a></li></ul></body></html>", siteUrl, externalShare.AcceptedAs, expiresIn.ToString(), cutoffDuration.ToString(), webUrl, shareRecord.UniqueIdentifier);
                                    Utility.SendEmail(clientContext, email);
                                    clientContext.ExecuteQuery();
                                }
                            }
                        }
                    }
                 }

                //delete all database records for this site that have an older processDate...these should represent external users deleted by manually
                using (ExternalSharingDataEntities entities = new ExternalSharingDataEntities())
                {
                    var cleanUpRecords = entities.ExternalShares.Where(i => i.SiteCollectionUrl.Equals(siteUrl.ToLower()) && i.LastProcessedDate < processDate);
                    foreach (var record in cleanUpRecords)
                    {
                        entities.ExternalShares.Remove(record);
                        entities.SaveChanges();
                    }
                }
            }
        }