Remove user from folder permission using MS Graph - c#

We have a C# application that is using the Microsoft Graph API to display the contents of SharePoint folder to users in a Syncfusion FileManager control. We need to grant permissions to those folders for certain users in order that they can collaborate on files.
We can add specific users using the sharing invitation to add a permission (see https://learn.microsoft.com/en-us/graph/api/driveitem-invite?view=graph-rest-1.0&tabs=http). We also need to be able to remove a user from this permission without deleting the whole link (and therefore any other users using it). I cannot see a way of doing this!
I have also tried using CreateLink (see https://learn.microsoft.com/en-us/graph/api/driveitem-createlink?view=graph-rest-1.0&tabs=http) but get an ‘Invalid Request’ error when trying to ‘Grant’ permission to a user and therefore never get as far as trying to remove an individual user!. The code I am using to try and 'Grant' permission is as follows (the last line produces the error):
public object CreateSharingLink(string itemId, List<string> recipientList, List<string> roles)
{
if (itemId == null) return null;
var type = "edit";
var scope = "organization";
Permission p = GraphClient.Drives[documentLibrary.Id].Items[itemId].CreateLink(type, scope).Request().PostAsync().Result;
return GrantAccessToSharingLink(p.Link.WebUrl, recipientList, roles);
}
public object GrantAccessToSharingLink(string sharingUrl, List<string> recipientList, List<string> roles)
{
List<DriveRecipient> recipients = (recipientList.Select(r => new DriveRecipient { Email = r })).ToList();
string base64Value = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sharingUrl));
string encodedUrl = "u!" + base64Value.TrimEnd('=').Replace('/', '_').Replace('+', '-');
return GraphClient.Shares[encodedUrl].Permission.Grant(roles, recipients).Request().PostAsync().Result;
}
Any assistance would be much appreciated.
A similar question was asked a while ago but without an answer. Remove GrantedTo user from Permission using Graph API

You can delete only not inherited permissions. Only sharing permissions that are not inherited can be deleted. Here's the related doc.

Related

Getting channels and looping through them discord.net

I am trying to create a commands using discord.net that will get a guild by id and then loop through the channels roles and users allowing me to list the names or permission each role or channel has. However, I've run into a few issues. The way I am currently getting my guild is using
var guild = BotStuff._client.GetGuild(Global.guildid);
After I get the guild I was able to get the number of channels, roles, or users using
var channels = guild.Channels;
and then
Console.WriteLine(channles.Count);
However, my main issue is that I want to be able to loop through the channels or roles and change the permissions or log the name of them, but I am unsure of how to accomplish this. I wasn't able to find documentation for this. Any help is appreciated!
I'll provide you with some examples that would already work with your current variables.
You need to gather the current channels in a guild, however since a Category is still considered a channel, you need to filter it out using the Where method in LINQ. This way, we will only have text and voice channels in our enumerable, which we then just loop through using the foreach keyword.
In the code examples below, you can modify the channel (name, positon, category) and add the permissions for both users and roles as you wanted.
In case you simply want to type their names into the console, just display the Name property in the foreach:
Console.WriteLine(channel.Name)
Change the name of each channel in a guild:
var guild = BotStuff._client.GetGuild(Global.guildid);
var channels = guild.Channels.Where(x => !(x is ICategoryChannel));
foreach (var channel in channels)
{
await channel.ModifyAsync(x =>
{
x.Name = "new name";
});
}
Add a permission overwrite for a user:
var guild = BotStuff._client.GetGuild(Global.guildid);
var channels = guild.Channels.Where(x => !(x is ICategoryChannel));
foreach (var channel in channels)
{
var guilduser = guild.GetUser(554240045800882181);
await channel.AddPermissionOverwriteAsync(guilduser, new OverwritePermissions(sendMessages: PermValue.Allow, manageChannel: PermValue.Allow));
}
This way we will let the user of ID 554240045800882181 send messages and manage the channel. You are able to choose between Allow, Inherit, Deny.
The permission adding method is overloaded and also accepts a role as an argument, so with a small modification we can change the channel permissions for a whole group of people.
var guildrole = guild.GetRole(202853174959144960);
await channel.AddPermissionOverwriteAsync(guildrole, new OverwritePermissions(manageMessages: PermValue.Allow, attachFiles: PermValue.Allow));
The IDs/variables used are just examples, you should understand and modify the code before using it.
For listing channel : (with a bot, else just replace Context.Guild.Channel per u'r getguild variable)
foreach (var channel in Context.Guild.Channels)
{
Console.WriteLine(channel.Name);
}

GroupPrincipal.FindByIdentity not getting the latest AD group

The issue I am having is that the newly added AD groups don't seem to be immediately searchable via GroupPrincipal.FindByIdentity() method.
Below is the code I used for adding the group.
// to add a new AD group
using (var group = new GroupPrincipal(context, groupName))
{
group.GroupScope = GroupScope.Universal;
group.Save();
}
One thing I'd like to mention here is that the PrincipalContext object context is only created once at the constructor of my AD service class, and I am reusing the same context object throughout the life of the service class. Not sure if this would cause any potential issues here.
So what can I do to get the most up-to-date AD groups right after the new groups are added?
I could achive this by adding a CommitChanges on the underlying directory entry.
public static void Test()
{
var context = PrincipalContextProvider.ProvideContext();
for (int i = 0; i < 10; i++)
{
using (var group = new GroupPrincipal(context, $"Hello_World_{i}"))
{
group.GroupScope = GroupScope.Universal;
group.Save();
((DirectoryEntry)group.GetUnderlyingObject()).CommitChanges();
}
var _group = GetGroup($"CN=Hello_World_{i},CN=Users,DC=abc,DC=com");
Console.WriteLine(_group.Name);
}
}
For explanation: The PrincipalContextProvider simply provides a context as you use in your code. The CommitChanges method, saves changes that are made to a directory entry to the underlying directory store. You also need the Save method! And at least, the GetGroups methods queries the group by its LDAP Path (its uses a new principal context and i'm not sure if it makes any differents). Hope it helps to go forward.

How to get All attributes from an Active Directory user in C#

I have been searching for quite some time for a solution using C# code that can query an Active Directory user for all the attributes it has registered to it, whether or not they have a NULL Value. These attributes are visible through the Attribute editor tab in the properties of the user in ADSI Edit on the domain server.
AD user attributes in ADSI edit
I need to dynamically retrieve these attributes, which means I probably can't reliably get these attribute names through the ADSI documentation on MSDN and because not all of these attributes might be user object specific:
https://msdn.microsoft.com/en-us/library/ms675090(v=vs.85).aspx
Here is what I have tried so far, but only got a fraction of the attributes of the user object:
PS command Get-ADUser -Identity administrator -Properties: This retrieved a good part of the attributes, but not nearly all of them and I do not know what .NET Classes and methods are invoked during this command, since TypeName = Microsoft.ActiveDirectory.Management.ADUser, which does not exist in the .NET framework. How can I see the specific methods that are using from .NET in PS?
C# calling this method:
public bool GetUserAttributes(out List<string> userAttributes, string userName)
{
userAttributes = new List<string>();
var valueReturn = false;
try
{
const string pathNameDomain = "LDAP://test.local";
var directoryEntry = new DirectoryEntry(pathNameDomain);
var directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = "(&(objectClass=user)(sAMAccountName=" + userName + "))"
};
var searchResults = directorySearcher.FindAll();
valueReturn = searchResults.Count > 0;
StreamWriter writer = new StreamWriter("C:\\LDAPGETUSERADEXAMPLE.txt");
foreach (SearchResult searchResult in searchResults)
{
foreach (var valueCollection in searchResult.Properties.PropertyNames)
{
userAttributes.Add(valueCollection.ToString() + " = " + searchResult.Properties[valueCollection.ToString()][0].ToString());
try
{
writer.WriteLine("Bruger attribut:" + valueCollection);
}
catch (Exception)
{
throw;
}
}
}
C# calling this method:
public List<string> GetADUserAttributes()
{
string objectDn = "CN=testuser,OU=TEST,DC=test,DC=local";
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
List<string> attributes = new List<string>();
foreach (string attribute in objRootDSE.Properties.PropertyNames)
{
attributes.Add(attribute);
}
return attributes;
}
What should I do to not filter out any attributes of the user object I am trying to retrieve from?
I am aware that Active Directory by default will only shows attributes that are default or have a value in them, I am trying to overcome this limitation.
EDIT 1:
I have temporarily postponed the specific question.
I have been trying to benchmark which of these methods are the fastest at retrieving (READ Operation) the SAM account name of 10.000 individual AD users called for example "testuser", the methods I benchmark are the following:
Time to complete: about 500 msec : ADSI - system.directoryservices
Time to complete: about 2700 msec: Principal - searcher system.directoryservices.accountmanagement
Time to complete: about NOT WORKING :LDAP - System.DirectoryServices.Protocols
Time to complete: about 60 msec : SQL - System.Data.SqlClient
I am querying for the user information from a workstation - Windows 10 machine in the domain I am querying. the workstation (4 vcpu), DC (2vpu) and DB (2vcpu) server is run as Hyper V vm's.
All attributes that any class can have are defined in Active Directory Schema
Use this to query for the user class. Then just call GetAllProperties method
var context = new DirectoryContext(DirectoryContextType.Forest, "amber.local");
using (var schema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetSchema(context))
{
var userClass = schema.FindClass("user");
foreach (ActiveDirectorySchemaProperty property in userClass.GetAllProperties())
{
// property.Name is what you're looking for
}
}
However AD schema may vary from one AD environment to another. For example, third party programs or Exchange Server may extend schema with custom attributes. It means that the solution with pre-defined columns will work only for a specific environment.

Add member to AD group from a trusted domain

I have two domains, in a trusted relationship, that I'm trying to manage from a C# web application. To do that, I have to impersonate two different technical users, but that works good, so I will not emphasize that part of the code.
To build proper and easy to manage ACLs for the file system, I must
Create a group in domainA (OK!)
Find a user in domainB (OK!)
Add the user to the group (FAILS when committing changes, error message: There is no such object on the server. (Exception from HRESULT: 0x80072030))
If I'm adding a user from the same domain, the code works perfectly, so I believe I'm only missing a small partial info here. I used this document as a reference and saw this question as well (and a few more citing this error message) but neither of them helped.
Code (try-catch block removed to make it simpler)
// de is a DirectoryEntry object of the AD group, received by the method as a parameter
// first impersonation to search in domainB
// works all right
if (impersonator.impersonateUser("techUser1", "domainB", "pass")) {
DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass");
de.Invoke("Add", new object[] { "LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" });
// de.Invoke("Add", new object[] { "LDAP://domainA.company.com/CN=anotherUserFromDomainA,OU=AnotherOU,DC=domainB,DC=company,DC=com" });
impersonator.undoImpersonation();
}
// second impersonation because the group (de) is in domainA
// and techUser2 has account operator privileges there
if (impersonator.impersonateUser("techUser2", "domainA", "pass"))
{
de.CommitChanges();
impersonator.undoImpersonation();
return true;
}
else
{
// second impersonation was unsuccessful, so return an empty object
return false;
}
Line 6 works, if I debug it or force the properties to be written to HttpResponse, it is clearly there. So the LDAP queries seem to be OK.
Also, if I comment out line 6 and uncomment 7, so basically I add a user from the same domain, the whole thing works miraculously. With domainB, I'm stuck. Any good piece of advice?
Following your code, I see that you're getting de as a parameter, which is in Domain A. Then you're creating DirectoryEntry object dom, which is getting impersonated, but never getting used. However, you're trying to add an object from Domain B to de directly using LDAP. This line:
de.Invoke("Add", new object[{"LDAP://domainB.company.com/CN=theUserIWantToAdd,OU=MyOU,DC=domainB,DC=company,DC=com" });
is not getting impersonated.
Assuming your impersonation works correctly, use dom object which is already impersonated with DirectorySearcher to find the user in Domain B and then add the user object from Domain B to de.
...
using (DirectoryEntry dom = new DirectoryEntry("LDAP://domainB.company.com/OU=MyOU,DC=domainB,DC=company,DC=com", "techUser1", "pass"))
{
using (DirectorySearcher searcher = new DirectorySearcher(dom))
{
searcher.Filter = "(&(objectClass=user)(CN=theUserIWantToAdd))";
SearchResult result = searcher.FindOne();
de.Invoke("Add", new object[] { result.Path });
}
}
...
UDPATE
This example will show you how to get user SID from one domain, search group from another domain and add user to group using SID.
//GET THE USER FROM DOMAIN B
using (UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(domainContext, UPN))
{
if (userPrincipal != null)
{
//FIND THE GROUP IN DOMAIN A
using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, groupName))
{
if (groupPrincipal != null)
{
//CHECK TO MAKE SURE USER IS NOT IN THAT GROUP
if (!userPrincipal.IsMemberOf(groupPrincipal))
{
string userSid = string.Format("<SID={0}>", userPrincipal.SID.ToString());
DirectoryEntry groupDirectoryEntry = (DirectoryEntry)groupPrincipal.GetUnderlyingObject();
groupDirectoryEntry.Properties["member"].Add(userSid);
groupDirectoryEntry.CommitChanges();
}
}
}
}
}
Please note that I skipped all the impersonation in the above code.
What finally worked was using principals as Burzum suggested. The original code samples you can see in the MSDN article linked in the question did not work here. So the Principal-based approach is a must nut not enough. You need one more line before committing changes of the new group:
group.Properties["groupType"].Value = (-2147483644);
The default was 0x8000000 and I had to change it to 0x80000004 to enable it to accept FSPs from another domain.
So now the group exists, it has members, it is added to the ACL of the folder.

C# google contact api deleted contact

currently I´m writing on a outlook plugin for syncing goolge contacts with outlook but I have to cover some special case:
When a contact gets deleted on google side, my application detects the missing contact and creates a new contact based on the contact info from the outlook one.
Is there a way to get an event or history from google that tells me a user deleted this contact(s)?
Edit 1:
Here is my code how I´m accessing the contacts (what is working FINE):
public GoogleAccessor()
{
var parameters = new OAuth2Parameters()
{
ClientId = CLIENTID,
ClientSecret = CLIENTSECRET,
RedirectUri = REDIRECTURI,
Scope = SCOPES
};
string url = OAuthUtil.CreateOAuth2AuthorizationUrl(parameters);
//An own webbrowser for processing the access tokens
IAuthorizationCodeProvider authcodeProvider = new Presentation.BrowserAuthorizationCodeProvider(new Presentation.BrowserAuthentificatorVM());
parameters.AccessCode = authcodeProvider.GetAuthorizationCode(url);
if(parameters.AccessCode == null)
throw new GoogleOAuthException("AccesCode returned 'null' and failed!");
OAuthUtil.GetAccessToken(parameters);
this._contactsRequest = new ContactsRequest(new RequestSettings(APPLICATIONNAME, parameters) {AutoPaging = true});
}
public IList<IContact> GetAllMappedContacts()
{
Feed<Google.Contacts.Contact> f = _contactsRequest.GetContacts();
this._feedUri = new Uri(f.AtomFeed.Feed);
var photoList = new List<PhotoObject>();
foreach (var entry in f.Entries)
{
var photoObject = GetContactPhoto(entry);
if(photoObject != null)
photoList.Add(photoObject);
}
_googleMapper = new GoogleMapper(f.Entries);
return _googleMapper.MapToLocalContacts();;
}
The thing about syncing in general is that syncing is normally meant to work in one direction.
Source Data -> Data Flow -> Received Data.
In this instance, Outlook is your source data and Google is your received data. All information needs to come from your source. Since this is an Outlook add-in you are creating my suggestion would be to add a button to your add-in ribbon. You can call the button whatever ever you like (maybe "dontSyncButton"), but it's purpose is going to be Categorization of your contact.
Make it so that that when a contact is selected and then the button is clicked, the contact is given a special categorization (perhaps "Dont Sync").
Now give some logic to your code that executes the sync, and have that logic decide whether to sync the contact. Also, give some logic to tell the program to delete the contact out of Google for you if the contacts contains the special category. Semi-Pseudo Code below:
if(contact.Categories.ToString() == "Dont Sync")
{
//Don't Sync Contact
If(googleContact.Exists())
{
//Delete contact from Google if it exist
googleContact.Delete();
}
}
else
{
//Sync Contact
}
It would be nice if Outlook had many modifiable properties that weren't visible to users, but since it does not this is really one of the best options I can think of. I do this to sync contacts from a shared Outlook folder to personal ones and it has worked well so far.
Hope this helps!

Categories

Resources