ms-active-directory


Namems-active-directory JSON
Version 1.14.1 PyPI version JSON
download
home_pagehttps://github.com/zorn96/ms_active_directory/
SummaryPython library for integrating with Microsoft Active Directory
upload_time2024-09-06 01:28:00
maintainerNone
docs_urlNone
authorAzaria Zornberg
requires_python>=3.5
licenseMIT License
keywords python3 ldap microsoft windows active-directory kerberos ad
VCS
bugtrack_url
requirements dnspython ldap3 pyasn1 pycryptodome pytz six
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # ms_active_directory - A Library for Integrating with Microsoft Active Directory
This is a library for integrating with Microsoft Active Directory domains.

It supports a variety of common, critical functionality for integration of computers
into a domain, including the ability to discover domain resources, optimize
communication for speed, join a computer to the domain, and look up information
about users and groups in the domain.

It primarily builds on the LDAP protocol, and supports LDAP over TLS with channel bindings,
and all LDAP basic, NTLM, and SASL authentication mechanisms (e.g. Kerberos) supported by the
`ldap3` python library.

For more detailed documentation, please see the docs at:\
**https://ms-active-directory.readthedocs.io/**

**Author**: Azaria Zornberg
\
**Email**: a.zornberg96@gmail.com

# Key Features
1. Joining a computer to a domain, either by creating a new computer account or taking over an existing
   account.
2. Discovering domain resources and properties, optimizing domain communication for the network
3. Discovering trusted domains, including MIT Kerberos domains and trusted Active Directory domains, and their
   attributes (e.g. are the trusts transitive? is SID filtering used? what direction is the trust?)
4. Transferring authenticated sessions from one domain to its trusted domains, allowing for easy querying
   capability across domains for widely trusted users. This can be used to trace foreign security principals
   across domains without needing to spin up and manage new domain objects for each.
5. Finding users, computers, and groups based on a variety of properties (e.g. name, SID, user-specified properties)
6. Querying information about users, computers, groups, and generic objects
7. Looking up user, computer, and group memberships
8. Looking up members of groups, regardless of type, and their attributes. This can also be done with auto-recursion
   for nested groups.
9. Adding and removing users, computers, and groups to and from other groups
10. Account management functionality for both users and computers, such as password changes/resets, enabling, disabling, and unlocking accounts
11. Looking up information about the permissions set on a user, computer, group, or generic object
12. Adding permissions to the security descriptor for a user, computer, group, or generic object
13. Support for creating users and groups
14. Support for updating attributes of users, computers, groups, and generic objects. Support exists for atomically appending 
    or overwriting values.
15. Support for finding policies within a domain, and for finding the policies directly attached to any given object.


# Examples

## Discovering a domain

The library supports discovering LDAP and Kerberos servers within a domain using special DNS
entries defined for Active Directory.

### Smart Defaults
By default, it will use the system DNS configuration, find LDAP servers that support TLS, and sort
LDAP and Kerberos servers by the RTT to communicate with them.
```
from ms_active_directory import ADDomain

example_domain_dns_name = 'example.com'
domain = ADDomain(example_domain_dns_name)
ldap_servers = domain.get_ldap_uris()
kerberos_servers = domain.get_kerberos_uris()

# re-discover servers in dns and sort them by RTT again at a later time to pick up changes
domain.refresh_ldap_server_discovery()
domain.refresh_kerberos_server_discovery()
```


### Site Awareness and Flexible DNS
The library also supports site awareness, which will result in only discovering servers within a specified
Active Directory Site. You can also specify alternative DNS nameservers to use instead of the system ones.
```
from ms_active_directory import ADDomain

example_domain_dns_name = 'example.com'
site_name = 'us-eastern-datacenter'
domain = ADDomain(example_domain_dns_name, site=site_name,
                  dns_nameservers=['eastern-private-dns-01.local'])
```

### Network Multi-Tenancy and Security Support
You can also specify exactly which LDAP or Kerberos servers should be used, and skip discovery.
Additional configurations are available such as configuring the CA file path to use for
trust, and the source IP to use for outbound traffic to the domain, which is helpful when
there are firewall rules in place, or when a machine has both private and public IP addresses.
```
from ms_active_directory import ADDomain

example_domain_dns_name = 'example.com'
local_machine_ip = '10.251.12.1'
local_ldap_ip = '10.251.12.30'
public_machine_ip = '194.32.21.30'
# the servers that live on the public internet use well-known public
# CAs for trust, but we have a local CA for the private network servers
private_securing_cas = '/etc/internal-ca.cert'

# set up an object for the local domain in the same network as this machine,
# but also have an instance that can be used to make instances to reach out
# to the rest of the domain outside of the local private network
local_domain = ADDomain(example_domain_dns_name, ldap_servers_or_uris=[local_ldap_ip],
                        source_ip=local_ldap_ip, ca_certificates_file_path=private_securing_cas)
global_domain = ADDomain(example_domain_dns_name, source_ip=public_machine_ip)
```

## Establishing a session with a domain

You can establish a session with the AD Domain on behalf of either a user or computer.

Broadly, any keyword arguments that would normally be supported when creating a `Connection`
with the `ldap3` library are supported when creating a session, allowing
for flexibility while still providing an "it just works" option for
most users.

### Support for Computer Authentication
Computers default to using Kerberos SASL authentication, as SIMPLE authentication is
not support for computers with Active Directory.
To use kerberos, either `gssapi` or `winkerberos` must be
installed.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')

# when using kerberos auth, the default is to use the kerberos
# credential cache on the machine, so no password is needed
computer_name = 'machine01'
session1 = domain.create_session_as_computer(computer_name)

# but you can pass sasl credentials, and if you use gssapi you can
# specify a username and password
# see the ldap3 documentation for details on SASL credentials and other
# connection options
other_name = 'other-machine-identity'
password = 'password01'
session2 = domain.create_session_as_computer(other_name, sasl_credentials=('', other_name, password))
```

You can also use other authentication mechanisms like NTLM.
``` 
from ldap3 import NTLM
from ms_active_directory import ADDomain
domain = ADDomain('example.com')

ntlm_name = 'EXAMPLE.COM\\computer01'
password = 'password1'
session = domain.create_session_as_computer(ntlm_name, password, authentication_mechanism=NTLM)
```

### Support for User Authentication

You can authenticate as a user by using simple binds, or by using SASL
mechanisms or NTLM as computers do.
The default for users is simple binds.
```
from ldap3 import NTLM
from ms_active_directory import ADDomain
domain = ADDomain('example.com')

session = domain.create_session_as_user('username@example.com', 'password')
ntlm_session =  domain.create_session_as_user('username@example.com', 'password',
                                              authentication_mechanism=NTLM)
```

## Discovering additional domain resources

The library supports discovering a wide variety of information about the domain beyond
the basics needed to communicate with it. This discovery doesn't require you to know any
niche information about Active Directory.

Discoverable resources include but are not limited to:
- Supported SASL mechanisms, which is important for authentication
- The current domain time, which is important for NTP synchronization
- Domain Functional Level, which governs things like support encryption types
- DNS servers
- Issuing certificates for CAs in the domain

### Finding supported SASL mechanisms
Discovering SASL mechanisms can be done without needing to create a session
with a domain, as it's needed before authentication in many cases.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com') 

# might print "['EXTERNAL', 'DIGEST-MD5']"
print(domain.find_supported_sasl_mechanisms())
```

### Finding the current domain time
Discovering the domain time can be done without needing to create a session
with a domain, as time synchronization is necessary for kerberos authentication
to succeed and can impact TLS negotiation as well.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com') 

# returns a python datetime object in utc time
curr_time = domain.find_current_time()

# allowed drift defaults to 5 minutes which is the kerberos standard,
# but we can use a shorter window to detect drift before it causes an
# outage. this returns a boolean
synced = domain.is_close_in_time_to_localhost(allowed_drift_seconds=60)
```

### Finding the domain functional level
Discovering the domain time can be done without needing to create a session
with a domain, as it can inform us as to what encryption types and TLS versions/ciphers
will be supported by the domain.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com') 

# find_functional_level returns an enum indicating the level.
# decision making based on level should be done based on the
# needs of your application
print(domain.find_functional_level())
```

### Finding DNS servers
Discovering DNS servers requires an authenticated session with the domain,
as searching the records within the domain for computers that run a DNS
service is privileged.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')

session = domain.create_session_as_user('username@example.com', 'password')
# returns a map that maps server hostnames -> ip addresses, where
# the hostnames are computers running dns services
dns_map = session.find_dns_servers_for_domain()
ip_addrs = dns_map.values()
hostnames = dns_map.keys()
```

### Finding CA certificates
Discovering DNS servers requires an authenticated session with the domain,
as searching the records within the domain for records that are indicated
as being certificate authorities is privileged.

```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')

session = domain.create_session_as_user('username@example.com', 'password')
# returns a list of PEM-formatted strings representing the signing certificates
# of all certificate authorities in the domain
pem_certs = session.find_certificate_authorities_for_domain()

# you can also get the certificates in DER format, which might be
# preferred on windows
der_certs = session.find_certificate_authorities_for_domain(pem_format=False)
```

## Joining an Active Directory domain

The action of joining a computer to a domain is not a well-defined operation,
and so the exact mechanics of how you utilize the domain joining functionality
and how its outputs are integrated with the rest of your system will vary depending on
your use case.

This will try to cover some common examples.

### Join the domain with default configurations for everything
The default behavior requires only the domain name and the credentials of a user with
sufficient administrative rights to create computers within the domain.
```
from ms_active_directory import join_ad_domain

comp = join_ad_domain('example.com', 'Administrator@example.com', 'example-password')
```
The `join_ad_domain` function returns a `ManagedADComputer` object with many helpful functions
describing properties of the created computer.

This will use the local hostname of the machine running this code as the computer name.
It will create the computer in AD's default `Computers` container.

It enables `AES256-SHA1` as an encryption type for both receiving and initiating kerberos
contexts, and it configures `<local hostname>.<domain dns name>` as the hostname of the
computer in AD and registers the default `HOST` service.

It then writes kerberos keys for the new computer account to `/etc/krb5.keytab`, which is
the default location for kerberos keytabs.

This all enables the account to be used for authenticating with other domain resources as a client
over protocols like SMB and LDAP using kerberos, as well as receiving incoming kerberos authentication
as a server for things like SSH. This is because the `HOST` service encapsulates many standard services
in the domain.

However, it is still up to the caller to do things like configure sshd to utilize the keytab.

### Join the domain with customization of the account for security reasons

A number of customizations exist for security reasons.

You can change things like the encryption types enabled on the account to support older clients.
You can also change location where the account is created when joining a domain in order to use
a less privileged user for the act of joining. Locations can be LDAP distinguished names or windows
path style canonical names.

You can also set the computer name if you have a desired naming scheme. This will impact the hostnames
configured in the domain for the computer.
```
from ms_active_directory import join_ad_domain, ADEncryptionType

domain = 'example.com'
less_privileged_user = 'ops-manager@example.com'
password = 'password2'
# ldap-style relative distinguished name of a location
less_privileged_loc = 'OU=service-machines,OU=ops'
computer_name = 'workstation10'

legacy_enc_type = ADEncryptionType.RC4_HMAC
new_enc_type = ADEncryptionType.AES256_CTS_HMAC_SHA1_96

comp = join_ad_domain(domain, less_privileged_user, password, computer_name=computer_name,
                      computer_location=less_privileged_loc, computer_encryption_types=[legacy_enc_type, new_enc_type])
                      
alt_format_loc = '/ops/service-machines'
comp = join_ad_domain(domain, less_privileged_user, password, computer_name=computer_name,
                      computer_location=alt_format_loc, computer_encryption_types=[legacy_enc_type, new_enc_type])

```

You can also manually set the computer password. The default is to generate a random 120
character password, but if you want to share this computer across services, and some cannot
interact with the generated kerberos keys, then you may wish to set a password manually.

You can also change where the kerberos keys are written to.
``` 
from ms_active_directory import join_ad_domain

domain = 'example.com'
user = 'ops-manager@example.com'
password = 'password2'
kerberos_key_location = '/usr/shared/keys/workstation-key.keytab'
computer_name = 'workstation10'
computer_password = 'workstation-shared-pw'

comp = join_ad_domain(domain, user, password, computer_key_file_path=kerberos_key_location,
                      computer_name=computer_name, computer_password=computer_password)
```

### Join the domain with different network or service settings
You can configure different hostnames for your computer when joining the
domain. This is useful when you have multiple different hostnames for
a single device, or want to use a computer name that doesn't match your
network name.

You can also configure services in order to restrict or broaden what is
supported by the computer when acting as a server (e.g. you can add `nfs`
if the machine will be an nfs server).

Joining will fail if another computer in the domain is using the services
you specify on any of the hostnames you specify in order to avoid conflicts
that cause undefined behavior.
``` 
from ms_active_directory import join_ad_domain

domain = 'example.com'
user = 'ops-manager@example.com'
password = 'password2'

services = ['HOST', 'nfs', 'cifs', 'HTTP']
computer_name = 'workstation10'
computer_host1 = 'central-mount-point.example.com'
computer_host2 = 'example-web-server.example.com'
comp = join_ad_domain(domain, user, password, computer_name=computer_name,
                      computer_hostnames=[computer_host1, computer_host2],
                      computer_services=services)
```


### Join using a domain object
You can use an `ADDomain` object to join the domain as well, using a `join` function.
This allows you to combine all of the functionality mentioned earlier around site-awareness,
server preferences, TLS settings, and network multi-tenancy with the domain joining
functionality mentioned in this section.

The parameters are all the same, except the domain need not be provided when using an
`ADDomain` object, so it just adds more functionality in exchange for a slightly less simple
workflow.

```
from ms_active_directory import ADDomain

domain = ADDomain('example.com', site='us-eastern-dc',
                  source_ip='10.25.21.30', dns_nameservers=['10.25.21.20'])

user = 'ops-manager@example.com'
password = 'password2'
less_privileged_loc = 'OU=service-machines,OU=ops'
services = ['HOST', 'nfs', 'cifs', 'HTTP']
computer_name = 'workstation10'

comp = domain.join(user, password, computer_hostnames=[computer_host1, computer_host2],
                   computer_services=services, computer_location=less_privileged_loc)
```

### Join the domain by taking over an existing account
For some setups, accounts may be pre-created and then taken over by the computers that will use them.

This can be done in order to greatly restrict the permissions of the user that is used for joining,
as they only need `RESET PASSWORD` permissions on the computer account, or `CHANGE PASSWORD` if
the current computer password is provided.
```
from ms_active_directory import ADDomain, join_ad_domain_by_taking_over_existing_computer

domain_dns_name = 'example.com'
site = 'us-eastern-dc'
existing_computer_name = 'precreated-comp'
user = 'single-account-admin@example.com'
password = 'password2'

computer_obj = join_ad_domain_by_taking_over_existing_computer(domain_dns_name, user, password,
                                                               ad_site=site, computer_name=existing_computer_name)
                                                               
# or use a domain object to use various power-user domain features
domain = ADDomain(domain_dns_name, site=site,
                  source_ip='10.25.21.30', dns_nameservers=['10.25.21.20'])
domain.join_by_taking_over_existing_computer(user, password, computer_name=existing_computer_name)
```

# Managing users, computers, and groups

The library provides a number of different functions for finding users, computers, and groups by different
identifiers, and for querying information about them.
It also has functions for checking their memberships and adding or removing users, computers, and groups
to or from groups.

## Looking up users, computers, groups, and information about them

Users, computers, and groups can both be looked up by one of:
- sAMAccountName
- distinguished name
- common name
- a generic "name" that will attempt the above 3
- an attribute

### Look up by sAMAccountName

A `sAMAccountName` is unique within a domain, and so looking up users or
groups by `sAMAccountName` returns a single result.
`sAMAccountName` was a user's windows logon name in older versions of windows,
and may be referred to as such in some documentation.

For computers, the standard convention is for their `sAMAccountName` to end with
a `$`, but many tools/docs leave that out. So if a `sAMAccountName` is specified
that does not end with a `$` and cannot be found, a lookup will also be
attempted after adding a `$` to the end.

When looking up users, computers, and groups, you can also query for additional information
about them by specifying a list of LDAP attributes.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user = session.find_user_by_sam_name('user1', ['employeeID'])
group = session.find_group_by_sam_name('group1', ['gidNumber'])
# users and groups support a generic "get" for any attributes queried
print(user.get('employeeID'))
print(group.get('gidNumber'))
```

### Look up by distinguished name

A distinguished name is unique within a forest, and so looking up users or
groups by it returns a single result.
A distinguished name should not be escaped when provided to the search function.

When looking up users, computers, and groups, you can also query for additional information
about them by specifying a list of LDAP attributes.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_dn = 'CN=user one,CN=Users,DC=example,DC=com'
user = session.find_user_by_distinguished_name(user_dn, ['employeeID'])
group_dn = 'CN=group one,OU=employee-groups,DC=example,DC=com'
group = session.find_group_by_distinguished_name(group_dn, ['gidNumber'])
# users and groups support a generic "get" for any attributes queried
print(user.get('employeeID'))
print(group.get('gidNumber'))
```

### Look up by common name
A common name is not unique within a domain, and so looking up users or
groups by it returns a list of results, which may have 0 or more entries.

When looking up users, computers, and groups, you can also query for additional information
about them by specifying a list of LDAP attributes.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_cn = 'John Doe'
users = session.find_users_by_common_name(user_cn, ['employeeID'])
group_dn = 'operations managers'
groups = session.find_groups_by_common_name(group_dn, ['gidNumber'])
# users and groups support a generic "get" for any attributes queried
for user in users:
    print(user.get('employeeID'))
for group in groups:
    print(group.get('gidNumber'))
```

### Look up by generic name
You can also query by a generic "name", and the library will attempt to find a
unique user or group with that name. The library will either lookup by DN or will
attempt `sAMAccountName` and common name lookups depending on the name format.

If more than one result is found by common name and no result is found by
`sAMAccountName` then this will produce an error.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_name = 'John Doe'
user = session.find_user_by_name(user_name, ['employeeID'])
group_name = 'operations managers'
groups = session.find_groups_by_name(group_name, ['gidNumber'])
# users and groups support a generic "get" for any attributes queried
print(user.get('employeeID'))
print(group.get('gidNumber'))
```

### Look up by attribute
You can also query for users, computers, or groups that possess a certain value for a
specified attribute. This can produce any number of results, so a list is
returned.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

desired_employee_type = 'temporary'
users = session.find_users_by_attribute('employeeType', desired_employee_type, ['employeeID'])
desired_group_manager = 'Alice P Hacker'
groups = session.find_groups_by_attribute('managedBy', desired_group_manager, ['gidNumber'])

# users and groups support a generic "get" for any attributes queried
for user in users:
    print(user.distinguished_name)
    print(user.get('employeeID'))
for group in groups:
    print(group.distinguished_name)
    print(group.get('gidNumber'))
```

## Querying user, computer, and group membership
You can also look up the groups that a user belongs to, the groups that a computer belongs to,
or the groups that a group belongs to. Active Directory supports nested groups, which is why
there's `user->groups`, `computer->groups`, and `group->groups` mapping capability.

When querying the membership information for users or groups, the input type for any
user or group must either be a string name identifying the user, computer, or group as described in the prior
section, or must be an `ADUser`, `ADComputer`, or `ADGroup` object returned by one of the functions described
in the prior section.

Similarly to looking up users, computers, and groups, you can query for attributes of the parent groups
by providing a list of LDAP attributes to look up for them.

``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_sam_account_name = 'user-sam-1'
user_dn = 'CN=user sam 1,CN=users,DC=example,DC=com'
user_cn = 'user same 1'

desired_group_attrs = ['gidNumber', 'managedBy']
# all 3 of these do the same thing, and internally map the different
# name types to a user object
groups_res1 = session.find_groups_for_user(user_sam_account_name, desired_group_attrs)
groups_res2 = session.find_groups_for_user(user_dn, desired_group_attrs)
groups_res3 = session.find_groups_for_user(user_cn, desired_group_attrs)

# you can also directly use a user object to query groups
user_obj = session.find_user_by_name(user_sam_account_name)
groups_res4 = session.find_groups_for_user(user_obj, desired_group_attrs)

# you can also look up the parents of groups in the same way
example_group_obj = groups_res4[0]
example_group_dn = example_group_obj.distinguished_name

# these both work. sAMAccountName could also be used, etc.
second_level_groups_res1 = session.find_groups_for_group(example_group_obj, desired_group_attrs)
second_level_groups_res2 = session.find_groups_for_group(example_group_dn, desired_group_attrs)
```

You can also query `users->groups`, `computers->groups`, and `groups->groups` to find the memberships of multiple
users, computers, and groups, and the library will make a minimal number of queries to determine membership;
it will be more efficient that doing a `user->groups` for each user (or similar for computers and groups).
The result will be a map that maps the input users or groups to lists of parent groups.

The input lists' elements must be the same format as what's provided when looking up group
memberships for a single user or group.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user1_name = 'user1'
user2_name = 'user2'
users = [user1_name, user2_name]
desired_group_attrs = ['gidNumber', 'managedBy']

user_group_map = session.find_groups_for_users(users, desired_group_attrs)
# the dictionary result keys are the users from the input
user1_groups = user_group_map[user1_name]
user2_groups = user_group_map[user2_name]

# you can use the groups->groups mapping functionality to enumerate the
# full tree of a users' group memberships (or a groups' group memberships)
user1_second_level_groups_map = session.find_groups_for_groups(user1_groups, desired_group_attrs)
all_second_level_groups = []
for group_list in user1_second_level_groups_map.values():
    for group in group_list:
        if group not in all_second_level_groups:
            all_second_level_groups.append(group)
all_user1_groups_in_2_levels = user1_groups + all_second_level_groups
```

## Finding the members of groups

You can look up the members of one or more groups and get attributes about those
members.
```
from ms_active_directory import ADDomain, ADUser, ADGroup
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

# get emails of users and groups that are members
desired_attrs = ['mail'] 

# look up members of a single group
single_group_member_list = session.find_members_of_group('group1', desired_attrs)

# look up members of multiple groups at once
groups = ['group1', 'group2']
group_to_member_list_map = session.find_members_of_groups(groups, desired_attrs)
group2_member_list = group_to_member_list_map['group2']
group2_user_members = [mem for mem in group2_member_list if isintance(mem, ADUser)]
group2_group_members = [mem for mem in group2_member_list if isintance(mem, ADGroup)]
```

You can also look up members recursively to handle nesting.
A maximum depth for lookups may be specified, but by default all
nesting will be enumerated.
``` 
from ms_active_directory import ADDomain, ADUser, ADGroup
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

# get emails of users and groups that are members
desired_attrs = ['mail'] 
group_name = 'has-groups-as-members'
groups_to_member_lists_maps = session.find_members_of_groups_recursive(group_name, desired_attrs)
```


## Adding users to groups
You can add users to groups by specifying a list of `ADUser` objects or string names of
AD users to be added to the groups, and a list of `ADGroup` objects or string names of AD
groups to add the users to.

If string names are specified, they'll be mapped to users/groups using the functions
discussed in the prior sections.

If a user is already in a group, this is idempotent and will not re-add them.

```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user1_name = 'user1'
user2_name = 'user2'
group1_name = 'target-group1'
group2_name = 'target-group2'

session.add_users_to_groups([user1_name, user2_name],
                            [group1_name, group2_name])
```

By default, if we fail to add users to one of the groups specified, we'll attempt to rollback
and remove users from any groups they were added to. You can choose to forgo this and a list of
groups that users were successfully added to will be returned instead.
``` 
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user1_name = 'user1'
user2_name = 'user2'
group1_name = 'target-group1'
group2_name = 'target-group2'
privileged_group = 'group-that-will-fail'

succeeeded = session.add_users_to_groups([user1_name, user2_name],
                                         [group1_name, group2_name, privileged_group],
                                         stop_and_rollback_on_error=False)
# this will print "['target-group1', 'target-group2']" assuming that
# adding users to 'group-that-will-fail' failed
print(succeeeded)                                 
```

## Adding groups to groups

Adding groups to other groups works exactly the same way as adding users to groups, but
the function is called `add_groups_to_groups` and both inputs are lists of groups.

## Adding computers to groups

Adding computers to groups works exactly the same way as adding users to groups, but
the function is called `add_computers_to_groups` and the first input is a list of computers.

## Removing users, computers, or groups from groups

Removing users, computers, or groups from groups works identically to adding users, computers, or groups to groups,
including input format, idempotency, and rollback functionality.
The only difference is that the functions are called `remove_users_from_groups`, `remove_computers_from_groups`, and
`remove_groups_from_groups` instead.


## Updating user, computer, or group attributes.
You can use this library to modify the values of various LDAP attributes on
users, computers, groups, or generic objects.

Users, computers, and groups provide the convenient name lookup functionality mentioned above,
while for generic objects you either need to pass an `ADObject` or a distinguished name.

### Appending to one or more attributes
You can atomically append values to multi-valued attributes, such as `accountNameHistory`.
This allows you to update their values without needing to know the current value or worry
about race conditions, as it's handled server-side.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_name = 'sarah1'
previous_account_name = 'sarah'
success = session.atomic_append_to_attribute_for_user(user_name, 'accountNameHistory',
                                                      previous_account_name)

# you can also append multiple values at once, or append to multiple
# attributes at once
user_name = 'monica pham-chen' 
previous_account_names = ['monica pham', 'monica chen']
previous_uid = 'mpham'
update_map = {
    'accountNameHistory': previous_account_names,
    'uid': previous_uid
}
success = session.atomic_append_to_attributes_for_user(user_name, update_map)
```
You can also perform these actions on groups and objects using the similarly named
functions `atomic_append_to_attribute_for_group`, `atomic_append_to_attributes_for_group`,
`atomic_append_to_attribute_for_computer`, `atomic_append_to_attributes_for_computer`,
`atomic_append_to_attribute_for_object`, and `atomic_append_to_attributes_for_object`.

### Overwriting one or more attributes
If you want to totally replace the value of an attribute, that's supported as well.
This can be done for single-valued or multi-valued attributes.

```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

user_name = 'arjun'
new_uid_number = 1093
success = session.overwrite_attribute_for_user(user_name, 'uidNumber',
                                               new_uid_number)
                                               
# just like appending, we can do multiple attributes at once atomically
user_name = 'nikita'
new_employee_type = 'Director'
new_gid = 0
new_addresses = [
   '123 mulberry lane',
   '456 vacation home drive'
]
new_value_map = {
   'employeeType': new_employee_type,
   'gidNumber': new_gid,
   'postalAddress': new_addresses
}
success = session.overwrite_attributes_for_user(user_name, new_value_map)
```
You can also perform these actions on groups and objects using the similarly named
functions, just like with appending.

# Discovering and Managing Trusted Domains

You can discover trusted domains using a session, and check properties about them.
```
from ms_active_directory import ADDomain
domain = ADDomain('example.com')
session = domain.create_session_as_user('username@example.com', 'password')

trusted_domains = session.find_trusted_domains_for_domain()

# split domains up based on trust type
trusted_mit_domains = [dom for dom in trusted_domains if dom.is_mit_trust()]
trusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()]

# print a few attributes that may be relevant
for ad_dom in trusted_ad_domains:
    print('FQDN: {}'.format(ad_dom.get_netbios_name()))
    print('Netbios name: {}'.format(ad_dom.get_netbios_name()))
    print('Disabled: {}'.format(ad_dom.is_disabled())
    print('Bi-directional: {}'.format(ad_dom.is_bidirectional_trust())
    print('Transitive: {}'.format(ad_dom.is_transitive_trust())
```

You can also convert AD domains that are trusted into fully usable `ADDomain`
objects for the purpose of creating sessions and looking up information there.
``` 
from ms_active_directory import ADDomain
from ldap3 import NTLM
domain = ADDomain('example.com')
widely_trusted_user = 'example.com\\org-admin'
password = 'password'

primary_session = domain.create_session_as_user(widely_trusted_user, password,
                                                authentication_mechanism=NTLM)

# get our trusted AD domains
trusted_domains = session.find_trusted_domains_for_domain()
trusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()]

# convert them into domains where our user should be trusted
domains_our_user_can_auth_with = []
for trusted_dom in trusted_ad_domains:
    if trusted_dom.trusts_primary_domain() and not trusted_dom.is_disabled():
        full_domain = trusted_dom.convert_to_ad_domain()
        domains_our_user_can_auth_with.append(full_domain)

# create sessions so we can search across many domains
all_user_sessions = [primary_session]
for dom in domains_our_user_can_auth_with:
    # SASL is needed for cross-domain authentication in general
    session = dom.create_session_as_user(widely_trusted_user, password,
                                         authentication_mechanism=NTLM)
    all_user_sessions.append(session)                                     
```

You can convert an existing authenticated session with one domain into an
authenticated session with a trusted AD domain that trusts the first domain.
```
from ms_active_directory import ADDomain
from ldap3 import NTLM
domain = ADDomain('example.com')
widely_trusted_user = 'example.com\\org-admin'
password = 'password'

primary_session = domain.create_session_as_user(widely_trusted_user, password,
                                                authentication_mechanism=NTLM)

# get our trusted AD domains
trusted_domains = session.find_trusted_domains_for_domain()
# filter for a domain being AD and it trusting the primary domain
trusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()
                      and dom.trusts_primary_domain()]

# create a new session with the trusted domain using our existing primary domain session,
# and use it to look up users/groups/etc. in the other domain
transferred_session = trusted_ad_domains[0].create_transfer_session_to_trusted_domain(primary_session)
transferred_session.find_user_by_name('other-domain-user')
```

You can also automatically have a session create sessions for all its trusted domains
that trust the session's domain.
```
from ms_active_directory import ADDomain
from ldap3 import NTLM
domain = ADDomain('example.com')
widely_trusted_user = 'example.com\\org-admin'
password = 'password'

primary_session = domain.create_session_as_user(widely_trusted_user, password,
                                                authentication_mechanism=NTLM)

# find a user that we know exists somewhere, but not the primary domain
user_to_find = 'some-lost-user'
# by default this filters to AD domains, and further filters to domains that trust the session's domain
# if the user used for the session is from the session's domain (which they are in this
# example)
trust_sessions = primary_session.create_transfer_sessions_to_all_trusted_domains()
user = None
for session in trust_sessions:
    user = session.find_user_by_name(user_to_find)
    if user is not None:
        print('Found user in {}'.format(session.get_domain_dns_name()))
        break
```


            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/zorn96/ms_active_directory/",
    "name": "ms-active-directory",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.5",
    "maintainer_email": null,
    "keywords": "python3 ldap microsoft windows active-directory kerberos ad",
    "author": "Azaria Zornberg",
    "author_email": "a.zornberg96@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/d0/a0/da7aaa5c87d155f2af60db0db3ab12eec50bfdeeca6f6cd1559ca92375c0/ms_active_directory-1.14.1.tar.gz",
    "platform": null,
    "description": "# ms_active_directory - A Library for Integrating with Microsoft Active Directory\nThis is a library for integrating with Microsoft Active Directory domains.\n\nIt supports a variety of common, critical functionality for integration of computers\ninto a domain, including the ability to discover domain resources, optimize\ncommunication for speed, join a computer to the domain, and look up information\nabout users and groups in the domain.\n\nIt primarily builds on the LDAP protocol, and supports LDAP over TLS with channel bindings,\nand all LDAP basic, NTLM, and SASL authentication mechanisms (e.g. Kerberos) supported by the\n`ldap3` python library.\n\nFor more detailed documentation, please see the docs at:\\\n**https://ms-active-directory.readthedocs.io/**\n\n**Author**: Azaria Zornberg\n\\\n**Email**: a.zornberg96@gmail.com\n\n# Key Features\n1. Joining a computer to a domain, either by creating a new computer account or taking over an existing\n   account.\n2. Discovering domain resources and properties, optimizing domain communication for the network\n3. Discovering trusted domains, including MIT Kerberos domains and trusted Active Directory domains, and their\n   attributes (e.g. are the trusts transitive? is SID filtering used? what direction is the trust?)\n4. Transferring authenticated sessions from one domain to its trusted domains, allowing for easy querying\n   capability across domains for widely trusted users. This can be used to trace foreign security principals\n   across domains without needing to spin up and manage new domain objects for each.\n5. Finding users, computers, and groups based on a variety of properties (e.g. name, SID, user-specified properties)\n6. Querying information about users, computers, groups, and generic objects\n7. Looking up user, computer, and group memberships\n8. Looking up members of groups, regardless of type, and their attributes. This can also be done with auto-recursion\n   for nested groups.\n9. Adding and removing users, computers, and groups to and from other groups\n10. Account management functionality for both users and computers, such as password changes/resets, enabling, disabling, and unlocking accounts\n11. Looking up information about the permissions set on a user, computer, group, or generic object\n12. Adding permissions to the security descriptor for a user, computer, group, or generic object\n13. Support for creating users and groups\n14. Support for updating attributes of users, computers, groups, and generic objects. Support exists for atomically appending \n    or overwriting values.\n15. Support for finding policies within a domain, and for finding the policies directly attached to any given object.\n\n\n# Examples\n\n## Discovering a domain\n\nThe library supports discovering LDAP and Kerberos servers within a domain using special DNS\nentries defined for Active Directory.\n\n### Smart Defaults\nBy default, it will use the system DNS configuration, find LDAP servers that support TLS, and sort\nLDAP and Kerberos servers by the RTT to communicate with them.\n```\nfrom ms_active_directory import ADDomain\n\nexample_domain_dns_name = 'example.com'\ndomain = ADDomain(example_domain_dns_name)\nldap_servers = domain.get_ldap_uris()\nkerberos_servers = domain.get_kerberos_uris()\n\n# re-discover servers in dns and sort them by RTT again at a later time to pick up changes\ndomain.refresh_ldap_server_discovery()\ndomain.refresh_kerberos_server_discovery()\n```\n\n\n### Site Awareness and Flexible DNS\nThe library also supports site awareness, which will result in only discovering servers within a specified\nActive Directory Site. You can also specify alternative DNS nameservers to use instead of the system ones.\n```\nfrom ms_active_directory import ADDomain\n\nexample_domain_dns_name = 'example.com'\nsite_name = 'us-eastern-datacenter'\ndomain = ADDomain(example_domain_dns_name, site=site_name,\n                  dns_nameservers=['eastern-private-dns-01.local'])\n```\n\n### Network Multi-Tenancy and Security Support\nYou can also specify exactly which LDAP or Kerberos servers should be used, and skip discovery.\nAdditional configurations are available such as configuring the CA file path to use for\ntrust, and the source IP to use for outbound traffic to the domain, which is helpful when\nthere are firewall rules in place, or when a machine has both private and public IP addresses.\n```\nfrom ms_active_directory import ADDomain\n\nexample_domain_dns_name = 'example.com'\nlocal_machine_ip = '10.251.12.1'\nlocal_ldap_ip = '10.251.12.30'\npublic_machine_ip = '194.32.21.30'\n# the servers that live on the public internet use well-known public\n# CAs for trust, but we have a local CA for the private network servers\nprivate_securing_cas = '/etc/internal-ca.cert'\n\n# set up an object for the local domain in the same network as this machine,\n# but also have an instance that can be used to make instances to reach out\n# to the rest of the domain outside of the local private network\nlocal_domain = ADDomain(example_domain_dns_name, ldap_servers_or_uris=[local_ldap_ip],\n                        source_ip=local_ldap_ip, ca_certificates_file_path=private_securing_cas)\nglobal_domain = ADDomain(example_domain_dns_name, source_ip=public_machine_ip)\n```\n\n## Establishing a session with a domain\n\nYou can establish a session with the AD Domain on behalf of either a user or computer.\n\nBroadly, any keyword arguments that would normally be supported when creating a `Connection`\nwith the `ldap3` library are supported when creating a session, allowing\nfor flexibility while still providing an \"it just works\" option for\nmost users.\n\n### Support for Computer Authentication\nComputers default to using Kerberos SASL authentication, as SIMPLE authentication is\nnot support for computers with Active Directory.\nTo use kerberos, either `gssapi` or `winkerberos` must be\ninstalled.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\n\n# when using kerberos auth, the default is to use the kerberos\n# credential cache on the machine, so no password is needed\ncomputer_name = 'machine01'\nsession1 = domain.create_session_as_computer(computer_name)\n\n# but you can pass sasl credentials, and if you use gssapi you can\n# specify a username and password\n# see the ldap3 documentation for details on SASL credentials and other\n# connection options\nother_name = 'other-machine-identity'\npassword = 'password01'\nsession2 = domain.create_session_as_computer(other_name, sasl_credentials=('', other_name, password))\n```\n\nYou can also use other authentication mechanisms like NTLM.\n``` \nfrom ldap3 import NTLM\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\n\nntlm_name = 'EXAMPLE.COM\\\\computer01'\npassword = 'password1'\nsession = domain.create_session_as_computer(ntlm_name, password, authentication_mechanism=NTLM)\n```\n\n### Support for User Authentication\n\nYou can authenticate as a user by using simple binds, or by using SASL\nmechanisms or NTLM as computers do.\nThe default for users is simple binds.\n```\nfrom ldap3 import NTLM\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\n\nsession = domain.create_session_as_user('username@example.com', 'password')\nntlm_session =  domain.create_session_as_user('username@example.com', 'password',\n                                              authentication_mechanism=NTLM)\n```\n\n## Discovering additional domain resources\n\nThe library supports discovering a wide variety of information about the domain beyond\nthe basics needed to communicate with it. This discovery doesn't require you to know any\nniche information about Active Directory.\n\nDiscoverable resources include but are not limited to:\n- Supported SASL mechanisms, which is important for authentication\n- The current domain time, which is important for NTP synchronization\n- Domain Functional Level, which governs things like support encryption types\n- DNS servers\n- Issuing certificates for CAs in the domain\n\n### Finding supported SASL mechanisms\nDiscovering SASL mechanisms can be done without needing to create a session\nwith a domain, as it's needed before authentication in many cases.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com') \n\n# might print \"['EXTERNAL', 'DIGEST-MD5']\"\nprint(domain.find_supported_sasl_mechanisms())\n```\n\n### Finding the current domain time\nDiscovering the domain time can be done without needing to create a session\nwith a domain, as time synchronization is necessary for kerberos authentication\nto succeed and can impact TLS negotiation as well.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com') \n\n# returns a python datetime object in utc time\ncurr_time = domain.find_current_time()\n\n# allowed drift defaults to 5 minutes which is the kerberos standard,\n# but we can use a shorter window to detect drift before it causes an\n# outage. this returns a boolean\nsynced = domain.is_close_in_time_to_localhost(allowed_drift_seconds=60)\n```\n\n### Finding the domain functional level\nDiscovering the domain time can be done without needing to create a session\nwith a domain, as it can inform us as to what encryption types and TLS versions/ciphers\nwill be supported by the domain.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com') \n\n# find_functional_level returns an enum indicating the level.\n# decision making based on level should be done based on the\n# needs of your application\nprint(domain.find_functional_level())\n```\n\n### Finding DNS servers\nDiscovering DNS servers requires an authenticated session with the domain,\nas searching the records within the domain for computers that run a DNS\nservice is privileged.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\n\nsession = domain.create_session_as_user('username@example.com', 'password')\n# returns a map that maps server hostnames -> ip addresses, where\n# the hostnames are computers running dns services\ndns_map = session.find_dns_servers_for_domain()\nip_addrs = dns_map.values()\nhostnames = dns_map.keys()\n```\n\n### Finding CA certificates\nDiscovering DNS servers requires an authenticated session with the domain,\nas searching the records within the domain for records that are indicated\nas being certificate authorities is privileged.\n\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\n\nsession = domain.create_session_as_user('username@example.com', 'password')\n# returns a list of PEM-formatted strings representing the signing certificates\n# of all certificate authorities in the domain\npem_certs = session.find_certificate_authorities_for_domain()\n\n# you can also get the certificates in DER format, which might be\n# preferred on windows\nder_certs = session.find_certificate_authorities_for_domain(pem_format=False)\n```\n\n## Joining an Active Directory domain\n\nThe action of joining a computer to a domain is not a well-defined operation,\nand so the exact mechanics of how you utilize the domain joining functionality\nand how its outputs are integrated with the rest of your system will vary depending on\nyour use case.\n\nThis will try to cover some common examples.\n\n### Join the domain with default configurations for everything\nThe default behavior requires only the domain name and the credentials of a user with\nsufficient administrative rights to create computers within the domain.\n```\nfrom ms_active_directory import join_ad_domain\n\ncomp = join_ad_domain('example.com', 'Administrator@example.com', 'example-password')\n```\nThe `join_ad_domain` function returns a `ManagedADComputer` object with many helpful functions\ndescribing properties of the created computer.\n\nThis will use the local hostname of the machine running this code as the computer name.\nIt will create the computer in AD's default `Computers` container.\n\nIt enables `AES256-SHA1` as an encryption type for both receiving and initiating kerberos\ncontexts, and it configures `<local hostname>.<domain dns name>` as the hostname of the\ncomputer in AD and registers the default `HOST` service.\n\nIt then writes kerberos keys for the new computer account to `/etc/krb5.keytab`, which is\nthe default location for kerberos keytabs.\n\nThis all enables the account to be used for authenticating with other domain resources as a client\nover protocols like SMB and LDAP using kerberos, as well as receiving incoming kerberos authentication\nas a server for things like SSH. This is because the `HOST` service encapsulates many standard services\nin the domain.\n\nHowever, it is still up to the caller to do things like configure sshd to utilize the keytab.\n\n### Join the domain with customization of the account for security reasons\n\nA number of customizations exist for security reasons.\n\nYou can change things like the encryption types enabled on the account to support older clients.\nYou can also change location where the account is created when joining a domain in order to use\na less privileged user for the act of joining. Locations can be LDAP distinguished names or windows\npath style canonical names.\n\nYou can also set the computer name if you have a desired naming scheme. This will impact the hostnames\nconfigured in the domain for the computer.\n```\nfrom ms_active_directory import join_ad_domain, ADEncryptionType\n\ndomain = 'example.com'\nless_privileged_user = 'ops-manager@example.com'\npassword = 'password2'\n# ldap-style relative distinguished name of a location\nless_privileged_loc = 'OU=service-machines,OU=ops'\ncomputer_name = 'workstation10'\n\nlegacy_enc_type = ADEncryptionType.RC4_HMAC\nnew_enc_type = ADEncryptionType.AES256_CTS_HMAC_SHA1_96\n\ncomp = join_ad_domain(domain, less_privileged_user, password, computer_name=computer_name,\n                      computer_location=less_privileged_loc, computer_encryption_types=[legacy_enc_type, new_enc_type])\n                      \nalt_format_loc = '/ops/service-machines'\ncomp = join_ad_domain(domain, less_privileged_user, password, computer_name=computer_name,\n                      computer_location=alt_format_loc, computer_encryption_types=[legacy_enc_type, new_enc_type])\n\n```\n\nYou can also manually set the computer password. The default is to generate a random 120\ncharacter password, but if you want to share this computer across services, and some cannot\ninteract with the generated kerberos keys, then you may wish to set a password manually.\n\nYou can also change where the kerberos keys are written to.\n``` \nfrom ms_active_directory import join_ad_domain\n\ndomain = 'example.com'\nuser = 'ops-manager@example.com'\npassword = 'password2'\nkerberos_key_location = '/usr/shared/keys/workstation-key.keytab'\ncomputer_name = 'workstation10'\ncomputer_password = 'workstation-shared-pw'\n\ncomp = join_ad_domain(domain, user, password, computer_key_file_path=kerberos_key_location,\n                      computer_name=computer_name, computer_password=computer_password)\n```\n\n### Join the domain with different network or service settings\nYou can configure different hostnames for your computer when joining the\ndomain. This is useful when you have multiple different hostnames for\na single device, or want to use a computer name that doesn't match your\nnetwork name.\n\nYou can also configure services in order to restrict or broaden what is\nsupported by the computer when acting as a server (e.g. you can add `nfs`\nif the machine will be an nfs server).\n\nJoining will fail if another computer in the domain is using the services\nyou specify on any of the hostnames you specify in order to avoid conflicts\nthat cause undefined behavior.\n``` \nfrom ms_active_directory import join_ad_domain\n\ndomain = 'example.com'\nuser = 'ops-manager@example.com'\npassword = 'password2'\n\nservices = ['HOST', 'nfs', 'cifs', 'HTTP']\ncomputer_name = 'workstation10'\ncomputer_host1 = 'central-mount-point.example.com'\ncomputer_host2 = 'example-web-server.example.com'\ncomp = join_ad_domain(domain, user, password, computer_name=computer_name,\n                      computer_hostnames=[computer_host1, computer_host2],\n                      computer_services=services)\n```\n\n\n### Join using a domain object\nYou can use an `ADDomain` object to join the domain as well, using a `join` function.\nThis allows you to combine all of the functionality mentioned earlier around site-awareness,\nserver preferences, TLS settings, and network multi-tenancy with the domain joining\nfunctionality mentioned in this section.\n\nThe parameters are all the same, except the domain need not be provided when using an\n`ADDomain` object, so it just adds more functionality in exchange for a slightly less simple\nworkflow.\n\n```\nfrom ms_active_directory import ADDomain\n\ndomain = ADDomain('example.com', site='us-eastern-dc',\n                  source_ip='10.25.21.30', dns_nameservers=['10.25.21.20'])\n\nuser = 'ops-manager@example.com'\npassword = 'password2'\nless_privileged_loc = 'OU=service-machines,OU=ops'\nservices = ['HOST', 'nfs', 'cifs', 'HTTP']\ncomputer_name = 'workstation10'\n\ncomp = domain.join(user, password, computer_hostnames=[computer_host1, computer_host2],\n                   computer_services=services, computer_location=less_privileged_loc)\n```\n\n### Join the domain by taking over an existing account\nFor some setups, accounts may be pre-created and then taken over by the computers that will use them.\n\nThis can be done in order to greatly restrict the permissions of the user that is used for joining,\nas they only need `RESET PASSWORD` permissions on the computer account, or `CHANGE PASSWORD` if\nthe current computer password is provided.\n```\nfrom ms_active_directory import ADDomain, join_ad_domain_by_taking_over_existing_computer\n\ndomain_dns_name = 'example.com'\nsite = 'us-eastern-dc'\nexisting_computer_name = 'precreated-comp'\nuser = 'single-account-admin@example.com'\npassword = 'password2'\n\ncomputer_obj = join_ad_domain_by_taking_over_existing_computer(domain_dns_name, user, password,\n                                                               ad_site=site, computer_name=existing_computer_name)\n                                                               \n# or use a domain object to use various power-user domain features\ndomain = ADDomain(domain_dns_name, site=site,\n                  source_ip='10.25.21.30', dns_nameservers=['10.25.21.20'])\ndomain.join_by_taking_over_existing_computer(user, password, computer_name=existing_computer_name)\n```\n\n# Managing users, computers, and groups\n\nThe library provides a number of different functions for finding users, computers, and groups by different\nidentifiers, and for querying information about them.\nIt also has functions for checking their memberships and adding or removing users, computers, and groups\nto or from groups.\n\n## Looking up users, computers, groups, and information about them\n\nUsers, computers, and groups can both be looked up by one of:\n- sAMAccountName\n- distinguished name\n- common name\n- a generic \"name\" that will attempt the above 3\n- an attribute\n\n### Look up by sAMAccountName\n\nA `sAMAccountName` is unique within a domain, and so looking up users or\ngroups by `sAMAccountName` returns a single result.\n`sAMAccountName` was a user's windows logon name in older versions of windows,\nand may be referred to as such in some documentation.\n\nFor computers, the standard convention is for their `sAMAccountName` to end with\na `$`, but many tools/docs leave that out. So if a `sAMAccountName` is specified\nthat does not end with a `$` and cannot be found, a lookup will also be\nattempted after adding a `$` to the end.\n\nWhen looking up users, computers, and groups, you can also query for additional information\nabout them by specifying a list of LDAP attributes.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser = session.find_user_by_sam_name('user1', ['employeeID'])\ngroup = session.find_group_by_sam_name('group1', ['gidNumber'])\n# users and groups support a generic \"get\" for any attributes queried\nprint(user.get('employeeID'))\nprint(group.get('gidNumber'))\n```\n\n### Look up by distinguished name\n\nA distinguished name is unique within a forest, and so looking up users or\ngroups by it returns a single result.\nA distinguished name should not be escaped when provided to the search function.\n\nWhen looking up users, computers, and groups, you can also query for additional information\nabout them by specifying a list of LDAP attributes.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_dn = 'CN=user one,CN=Users,DC=example,DC=com'\nuser = session.find_user_by_distinguished_name(user_dn, ['employeeID'])\ngroup_dn = 'CN=group one,OU=employee-groups,DC=example,DC=com'\ngroup = session.find_group_by_distinguished_name(group_dn, ['gidNumber'])\n# users and groups support a generic \"get\" for any attributes queried\nprint(user.get('employeeID'))\nprint(group.get('gidNumber'))\n```\n\n### Look up by common name\nA common name is not unique within a domain, and so looking up users or\ngroups by it returns a list of results, which may have 0 or more entries.\n\nWhen looking up users, computers, and groups, you can also query for additional information\nabout them by specifying a list of LDAP attributes.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_cn = 'John Doe'\nusers = session.find_users_by_common_name(user_cn, ['employeeID'])\ngroup_dn = 'operations managers'\ngroups = session.find_groups_by_common_name(group_dn, ['gidNumber'])\n# users and groups support a generic \"get\" for any attributes queried\nfor user in users:\n    print(user.get('employeeID'))\nfor group in groups:\n    print(group.get('gidNumber'))\n```\n\n### Look up by generic name\nYou can also query by a generic \"name\", and the library will attempt to find a\nunique user or group with that name. The library will either lookup by DN or will\nattempt `sAMAccountName` and common name lookups depending on the name format.\n\nIf more than one result is found by common name and no result is found by\n`sAMAccountName` then this will produce an error.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_name = 'John Doe'\nuser = session.find_user_by_name(user_name, ['employeeID'])\ngroup_name = 'operations managers'\ngroups = session.find_groups_by_name(group_name, ['gidNumber'])\n# users and groups support a generic \"get\" for any attributes queried\nprint(user.get('employeeID'))\nprint(group.get('gidNumber'))\n```\n\n### Look up by attribute\nYou can also query for users, computers, or groups that possess a certain value for a\nspecified attribute. This can produce any number of results, so a list is\nreturned.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\ndesired_employee_type = 'temporary'\nusers = session.find_users_by_attribute('employeeType', desired_employee_type, ['employeeID'])\ndesired_group_manager = 'Alice P Hacker'\ngroups = session.find_groups_by_attribute('managedBy', desired_group_manager, ['gidNumber'])\n\n# users and groups support a generic \"get\" for any attributes queried\nfor user in users:\n    print(user.distinguished_name)\n    print(user.get('employeeID'))\nfor group in groups:\n    print(group.distinguished_name)\n    print(group.get('gidNumber'))\n```\n\n## Querying user, computer, and group membership\nYou can also look up the groups that a user belongs to, the groups that a computer belongs to,\nor the groups that a group belongs to. Active Directory supports nested groups, which is why\nthere's `user->groups`, `computer->groups`, and `group->groups` mapping capability.\n\nWhen querying the membership information for users or groups, the input type for any\nuser or group must either be a string name identifying the user, computer, or group as described in the prior\nsection, or must be an `ADUser`, `ADComputer`, or `ADGroup` object returned by one of the functions described\nin the prior section.\n\nSimilarly to looking up users, computers, and groups, you can query for attributes of the parent groups\nby providing a list of LDAP attributes to look up for them.\n\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_sam_account_name = 'user-sam-1'\nuser_dn = 'CN=user sam 1,CN=users,DC=example,DC=com'\nuser_cn = 'user same 1'\n\ndesired_group_attrs = ['gidNumber', 'managedBy']\n# all 3 of these do the same thing, and internally map the different\n# name types to a user object\ngroups_res1 = session.find_groups_for_user(user_sam_account_name, desired_group_attrs)\ngroups_res2 = session.find_groups_for_user(user_dn, desired_group_attrs)\ngroups_res3 = session.find_groups_for_user(user_cn, desired_group_attrs)\n\n# you can also directly use a user object to query groups\nuser_obj = session.find_user_by_name(user_sam_account_name)\ngroups_res4 = session.find_groups_for_user(user_obj, desired_group_attrs)\n\n# you can also look up the parents of groups in the same way\nexample_group_obj = groups_res4[0]\nexample_group_dn = example_group_obj.distinguished_name\n\n# these both work. sAMAccountName could also be used, etc.\nsecond_level_groups_res1 = session.find_groups_for_group(example_group_obj, desired_group_attrs)\nsecond_level_groups_res2 = session.find_groups_for_group(example_group_dn, desired_group_attrs)\n```\n\nYou can also query `users->groups`, `computers->groups`, and `groups->groups` to find the memberships of multiple\nusers, computers, and groups, and the library will make a minimal number of queries to determine membership;\nit will be more efficient that doing a `user->groups` for each user (or similar for computers and groups).\nThe result will be a map that maps the input users or groups to lists of parent groups.\n\nThe input lists' elements must be the same format as what's provided when looking up group\nmemberships for a single user or group.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser1_name = 'user1'\nuser2_name = 'user2'\nusers = [user1_name, user2_name]\ndesired_group_attrs = ['gidNumber', 'managedBy']\n\nuser_group_map = session.find_groups_for_users(users, desired_group_attrs)\n# the dictionary result keys are the users from the input\nuser1_groups = user_group_map[user1_name]\nuser2_groups = user_group_map[user2_name]\n\n# you can use the groups->groups mapping functionality to enumerate the\n# full tree of a users' group memberships (or a groups' group memberships)\nuser1_second_level_groups_map = session.find_groups_for_groups(user1_groups, desired_group_attrs)\nall_second_level_groups = []\nfor group_list in user1_second_level_groups_map.values():\n    for group in group_list:\n        if group not in all_second_level_groups:\n            all_second_level_groups.append(group)\nall_user1_groups_in_2_levels = user1_groups + all_second_level_groups\n```\n\n## Finding the members of groups\n\nYou can look up the members of one or more groups and get attributes about those\nmembers.\n```\nfrom ms_active_directory import ADDomain, ADUser, ADGroup\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\n# get emails of users and groups that are members\ndesired_attrs = ['mail'] \n\n# look up members of a single group\nsingle_group_member_list = session.find_members_of_group('group1', desired_attrs)\n\n# look up members of multiple groups at once\ngroups = ['group1', 'group2']\ngroup_to_member_list_map = session.find_members_of_groups(groups, desired_attrs)\ngroup2_member_list = group_to_member_list_map['group2']\ngroup2_user_members = [mem for mem in group2_member_list if isintance(mem, ADUser)]\ngroup2_group_members = [mem for mem in group2_member_list if isintance(mem, ADGroup)]\n```\n\nYou can also look up members recursively to handle nesting.\nA maximum depth for lookups may be specified, but by default all\nnesting will be enumerated.\n``` \nfrom ms_active_directory import ADDomain, ADUser, ADGroup\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\n# get emails of users and groups that are members\ndesired_attrs = ['mail'] \ngroup_name = 'has-groups-as-members'\ngroups_to_member_lists_maps = session.find_members_of_groups_recursive(group_name, desired_attrs)\n```\n\n\n## Adding users to groups\nYou can add users to groups by specifying a list of `ADUser` objects or string names of\nAD users to be added to the groups, and a list of `ADGroup` objects or string names of AD\ngroups to add the users to.\n\nIf string names are specified, they'll be mapped to users/groups using the functions\ndiscussed in the prior sections.\n\nIf a user is already in a group, this is idempotent and will not re-add them.\n\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser1_name = 'user1'\nuser2_name = 'user2'\ngroup1_name = 'target-group1'\ngroup2_name = 'target-group2'\n\nsession.add_users_to_groups([user1_name, user2_name],\n                            [group1_name, group2_name])\n```\n\nBy default, if we fail to add users to one of the groups specified, we'll attempt to rollback\nand remove users from any groups they were added to. You can choose to forgo this and a list of\ngroups that users were successfully added to will be returned instead.\n``` \nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser1_name = 'user1'\nuser2_name = 'user2'\ngroup1_name = 'target-group1'\ngroup2_name = 'target-group2'\nprivileged_group = 'group-that-will-fail'\n\nsucceeeded = session.add_users_to_groups([user1_name, user2_name],\n                                         [group1_name, group2_name, privileged_group],\n                                         stop_and_rollback_on_error=False)\n# this will print \"['target-group1', 'target-group2']\" assuming that\n# adding users to 'group-that-will-fail' failed\nprint(succeeeded)                                 \n```\n\n## Adding groups to groups\n\nAdding groups to other groups works exactly the same way as adding users to groups, but\nthe function is called `add_groups_to_groups` and both inputs are lists of groups.\n\n## Adding computers to groups\n\nAdding computers to groups works exactly the same way as adding users to groups, but\nthe function is called `add_computers_to_groups` and the first input is a list of computers.\n\n## Removing users, computers, or groups from groups\n\nRemoving users, computers, or groups from groups works identically to adding users, computers, or groups to groups,\nincluding input format, idempotency, and rollback functionality.\nThe only difference is that the functions are called `remove_users_from_groups`, `remove_computers_from_groups`, and\n`remove_groups_from_groups` instead.\n\n\n## Updating user, computer, or group attributes.\nYou can use this library to modify the values of various LDAP attributes on\nusers, computers, groups, or generic objects.\n\nUsers, computers, and groups provide the convenient name lookup functionality mentioned above,\nwhile for generic objects you either need to pass an `ADObject` or a distinguished name.\n\n### Appending to one or more attributes\nYou can atomically append values to multi-valued attributes, such as `accountNameHistory`.\nThis allows you to update their values without needing to know the current value or worry\nabout race conditions, as it's handled server-side.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_name = 'sarah1'\nprevious_account_name = 'sarah'\nsuccess = session.atomic_append_to_attribute_for_user(user_name, 'accountNameHistory',\n                                                      previous_account_name)\n\n# you can also append multiple values at once, or append to multiple\n# attributes at once\nuser_name = 'monica pham-chen' \nprevious_account_names = ['monica pham', 'monica chen']\nprevious_uid = 'mpham'\nupdate_map = {\n    'accountNameHistory': previous_account_names,\n    'uid': previous_uid\n}\nsuccess = session.atomic_append_to_attributes_for_user(user_name, update_map)\n```\nYou can also perform these actions on groups and objects using the similarly named\nfunctions `atomic_append_to_attribute_for_group`, `atomic_append_to_attributes_for_group`,\n`atomic_append_to_attribute_for_computer`, `atomic_append_to_attributes_for_computer`,\n`atomic_append_to_attribute_for_object`, and `atomic_append_to_attributes_for_object`.\n\n### Overwriting one or more attributes\nIf you want to totally replace the value of an attribute, that's supported as well.\nThis can be done for single-valued or multi-valued attributes.\n\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\nuser_name = 'arjun'\nnew_uid_number = 1093\nsuccess = session.overwrite_attribute_for_user(user_name, 'uidNumber',\n                                               new_uid_number)\n                                               \n# just like appending, we can do multiple attributes at once atomically\nuser_name = 'nikita'\nnew_employee_type = 'Director'\nnew_gid = 0\nnew_addresses = [\n   '123 mulberry lane',\n   '456 vacation home drive'\n]\nnew_value_map = {\n   'employeeType': new_employee_type,\n   'gidNumber': new_gid,\n   'postalAddress': new_addresses\n}\nsuccess = session.overwrite_attributes_for_user(user_name, new_value_map)\n```\nYou can also perform these actions on groups and objects using the similarly named\nfunctions, just like with appending.\n\n# Discovering and Managing Trusted Domains\n\nYou can discover trusted domains using a session, and check properties about them.\n```\nfrom ms_active_directory import ADDomain\ndomain = ADDomain('example.com')\nsession = domain.create_session_as_user('username@example.com', 'password')\n\ntrusted_domains = session.find_trusted_domains_for_domain()\n\n# split domains up based on trust type\ntrusted_mit_domains = [dom for dom in trusted_domains if dom.is_mit_trust()]\ntrusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()]\n\n# print a few attributes that may be relevant\nfor ad_dom in trusted_ad_domains:\n    print('FQDN: {}'.format(ad_dom.get_netbios_name()))\n    print('Netbios name: {}'.format(ad_dom.get_netbios_name()))\n    print('Disabled: {}'.format(ad_dom.is_disabled())\n    print('Bi-directional: {}'.format(ad_dom.is_bidirectional_trust())\n    print('Transitive: {}'.format(ad_dom.is_transitive_trust())\n```\n\nYou can also convert AD domains that are trusted into fully usable `ADDomain`\nobjects for the purpose of creating sessions and looking up information there.\n``` \nfrom ms_active_directory import ADDomain\nfrom ldap3 import NTLM\ndomain = ADDomain('example.com')\nwidely_trusted_user = 'example.com\\\\org-admin'\npassword = 'password'\n\nprimary_session = domain.create_session_as_user(widely_trusted_user, password,\n                                                authentication_mechanism=NTLM)\n\n# get our trusted AD domains\ntrusted_domains = session.find_trusted_domains_for_domain()\ntrusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()]\n\n# convert them into domains where our user should be trusted\ndomains_our_user_can_auth_with = []\nfor trusted_dom in trusted_ad_domains:\n    if trusted_dom.trusts_primary_domain() and not trusted_dom.is_disabled():\n        full_domain = trusted_dom.convert_to_ad_domain()\n        domains_our_user_can_auth_with.append(full_domain)\n\n# create sessions so we can search across many domains\nall_user_sessions = [primary_session]\nfor dom in domains_our_user_can_auth_with:\n    # SASL is needed for cross-domain authentication in general\n    session = dom.create_session_as_user(widely_trusted_user, password,\n                                         authentication_mechanism=NTLM)\n    all_user_sessions.append(session)                                     \n```\n\nYou can convert an existing authenticated session with one domain into an\nauthenticated session with a trusted AD domain that trusts the first domain.\n```\nfrom ms_active_directory import ADDomain\nfrom ldap3 import NTLM\ndomain = ADDomain('example.com')\nwidely_trusted_user = 'example.com\\\\org-admin'\npassword = 'password'\n\nprimary_session = domain.create_session_as_user(widely_trusted_user, password,\n                                                authentication_mechanism=NTLM)\n\n# get our trusted AD domains\ntrusted_domains = session.find_trusted_domains_for_domain()\n# filter for a domain being AD and it trusting the primary domain\ntrusted_ad_domains = [dom for dom in trusted_domains if dom.is_active_directory_domain_trust()\n                      and dom.trusts_primary_domain()]\n\n# create a new session with the trusted domain using our existing primary domain session,\n# and use it to look up users/groups/etc. in the other domain\ntransferred_session = trusted_ad_domains[0].create_transfer_session_to_trusted_domain(primary_session)\ntransferred_session.find_user_by_name('other-domain-user')\n```\n\nYou can also automatically have a session create sessions for all its trusted domains\nthat trust the session's domain.\n```\nfrom ms_active_directory import ADDomain\nfrom ldap3 import NTLM\ndomain = ADDomain('example.com')\nwidely_trusted_user = 'example.com\\\\org-admin'\npassword = 'password'\n\nprimary_session = domain.create_session_as_user(widely_trusted_user, password,\n                                                authentication_mechanism=NTLM)\n\n# find a user that we know exists somewhere, but not the primary domain\nuser_to_find = 'some-lost-user'\n# by default this filters to AD domains, and further filters to domains that trust the session's domain\n# if the user used for the session is from the session's domain (which they are in this\n# example)\ntrust_sessions = primary_session.create_transfer_sessions_to_all_trusted_domains()\nuser = None\nfor session in trust_sessions:\n    user = session.find_user_by_name(user_to_find)\n    if user is not None:\n        print('Found user in {}'.format(session.get_domain_dns_name()))\n        break\n```\n\n",
    "bugtrack_url": null,
    "license": "MIT License",
    "summary": "Python library for integrating with Microsoft Active Directory",
    "version": "1.14.1",
    "project_urls": {
        "Homepage": "https://github.com/zorn96/ms_active_directory/"
    },
    "split_keywords": [
        "python3",
        "ldap",
        "microsoft",
        "windows",
        "active-directory",
        "kerberos",
        "ad"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d0a0da7aaa5c87d155f2af60db0db3ab12eec50bfdeeca6f6cd1559ca92375c0",
                "md5": "1863731e77212104f0f7fa8ff316a310",
                "sha256": "86c3b9de8b8b5546104f0fe480db689b1a1d0a4109a4208603be4f981ce12040"
            },
            "downloads": -1,
            "filename": "ms_active_directory-1.14.1.tar.gz",
            "has_sig": false,
            "md5_digest": "1863731e77212104f0f7fa8ff316a310",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.5",
            "size": 155782,
            "upload_time": "2024-09-06T01:28:00",
            "upload_time_iso_8601": "2024-09-06T01:28:00.609736Z",
            "url": "https://files.pythonhosted.org/packages/d0/a0/da7aaa5c87d155f2af60db0db3ab12eec50bfdeeca6f6cd1559ca92375c0/ms_active_directory-1.14.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-09-06 01:28:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "zorn96",
    "github_project": "ms_active_directory",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "dnspython",
            "specs": [
                [
                    ">=",
                    "2.1.0"
                ]
            ]
        },
        {
            "name": "ldap3",
            "specs": [
                [
                    ">=",
                    "2.8.0"
                ]
            ]
        },
        {
            "name": "pyasn1",
            "specs": [
                [
                    ">=",
                    "0.4.6"
                ]
            ]
        },
        {
            "name": "pycryptodome",
            "specs": [
                [
                    ">=",
                    "3.9.0"
                ]
            ]
        },
        {
            "name": "pytz",
            "specs": []
        },
        {
            "name": "six",
            "specs": [
                [
                    ">=",
                    "1.15.0"
                ]
            ]
        }
    ],
    "lcname": "ms-active-directory"
}
        
Elapsed time: 0.36313s