Repository: ansible/ansible

Issue:
Authentication fails for federated Azure tenants
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
-->

##### ISSUE TYPE
Bug Report

##### COMPONENT NAME
ansible_module_azure_rm_*

##### ANSIBLE VERSION
```
ansible 2.5.0b2
  config file = /home/ubuntu/azure/ansible/ansible.cfg
  configured module search path = [u'/home/ubuntu/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
```
ANSIBLE_FORCE_COLOR(env: ANSIBLE_FORCE_COLOR) = True
ANSIBLE_SSH_ARGS(/home/ubuntu/azure/ansible/ansible.cfg) = -o "StrictHostKeyChecking no" -o "UserKnownHostsFile /dev/null"
DEFAULT_CALLBACK_WHITELIST(/home/ubuntu/azure/ansible/ansible.cfg) = ['profile_tasks']
```

##### OS / ENVIRONMENT
N/A

##### SUMMARY
Azure Module cannot authenticate users when using federated Azure tenant

##### STEPS TO REPRODUCE
Use a federated tenant that requires the authentication to a on-premise AD service and a valid user/password.

<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```
declare -x AZURE_AD_USER="user@domain"
declare -x AZURE_PASSWORD="xxx"
declare -x AZURE_SUBSCRIPTION_ID="UUID_of_suscription"
declare -x AZURE_TENANT_ID="UUID_of_tenant"

ansible -vvv -i inventory_azure provision.yml
```


##### EXPECTED RESULTS
Authentication of module works and and results in allocation of public ip.


##### ACTUAL RESULTS

<!--- Paste verbatim command output between quotes below -->
```
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py", line 276, in <module>
    main()
  File "/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py", line 273, in main
    AzureRMPublicIPAddress()
  File "/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py", line 169, in __init__
    supports_check_mode=True)
  File "/tmp/ansible_jGyGkY/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py", line 334, in __init__
  File "/usr/local/lib/python2.7/dist-packages/msrestazure/azure_active_directory.py", line 367, in __init__
    self.set_token()
  File "/usr/local/lib/python2.7/dist-packages/msrestazure/azure_active_directory.py", line 404, in set_token
    raise_with_traceback(AuthenticationError, "", err)
  File "/usr/local/lib/python2.7/dist-packages/msrest/exceptions.py", line 48, in raise_with_traceback
    raise error
msrest.exceptions.AuthenticationError: , InvalidGrantError: (invalid_grant) AADSTS70002: Error validating credentials. AADSTS50126: Invalid username or password
Trace ID: 484222f4-b98f-4bb5-a831-97ed7efb1400
Correlation ID: 1172590a-9cb3-4357-b0d0-512b31128e07
Timestamp: 2018-02-23 09:05:33Z

fatal: [linuxhost1]: FAILED! => {
    "changed": false,
    "failed": true,
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py\", line 276, in <module>\n    main()\n  File \"/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py\", line 273, in main\n    AzureRMPublicIPAddress()\n  File \"/tmp/ansible_jGyGkY/ansible_module_azure_rm_publicipaddress.py\", line 169, in __init__\n    supports_check_mode=True)\n  File \"/tmp/ansible_jGyGkY/ansible_modlib.zip/ansible/module_utils/azure_rm_common.py\", line 334, in __init__\n  File \"/usr/local/lib/python2.7/dist-packages/msrestazure/azure_active_directory.py\", line 367, in __init__\n    self.set_token()\n  File \"/usr/local/lib/python2.7/dist-packages/msrestazure/azure_active_directory.py\", line 404, in set_token\n    raise_with_traceback(AuthenticationError, \"\", err)\n  File \"/usr/local/lib/python2.7/dist-packages/msrest/exceptions.py\", line 48, in raise_with_traceback\n    raise error\nmsrest.exceptions.AuthenticationError: , InvalidGrantError: (invalid_grant) AADSTS70002: Error validating credentials. AADSTS50126: Invalid username or password\r\nTrace ID: 484222f4-b98f-4bb5-a831-97ed7efb1400\r\nCorrelation ID: 1172590a-9cb3-4357-b0d0-512b31128e07\r\nTimestamp: 2018-02-23 09:05:33Z\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "rc": 1
}

```



Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/azure/azure_rm_acs.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_acs.py)
* [lib/ansible/modules/cloud/azure/azure_rm_availabilityset.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_availabilityset.py)
* [lib/ansible/modules/cloud/azure/azure_rm_availabilityset_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_availabilityset_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_containerinstance.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_containerinstance.py)
* [lib/ansible/modules/cloud/azure/azure_rm_containerregistry.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_containerregistry.py)
* [lib/ansible/modules/cloud/azure/azure_rm_deployment.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_deployment.py)
* [lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset.py)
* [lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_dnsrecordset_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_dnszone.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_dnszone.py)
* [lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_dnszone_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_functionapp.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_functionapp.py)
* [lib/ansible/modules/cloud/azure/azure_rm_functionapp_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_functionapp_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_image.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_image.py)
* [lib/ansible/modules/cloud/azure/azure_rm_keyvault.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_keyvault.py)
* [lib/ansible/modules/cloud/azure/azure_rm_keyvaultkey.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_keyvaultkey.py)
* [lib/ansible/modules/cloud/azure/azure_rm_keyvaultsecret.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_keyvaultsecret.py)
* [lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py)
* [lib/ansible/modules/cloud/azure/azure_rm_loadbalancer_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_managed_disk.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_managed_disk.py)
* [lib/ansible/modules/cloud/azure/azure_rm_managed_disk_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_managed_disk_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_mysqldatabase.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_mysqldatabase.py)
* [lib/ansible/modules/cloud/azure/azure_rm_mysqlserver.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_mysqlserver.py)
* [lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_networkinterface.py)
* [lib/ansible/modules/cloud/azure/azure_rm_networkinterface_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_networkinterface_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_postgresqldatabase.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_postgresqldatabase.py)
* [lib/ansible/modules/cloud/azure/azure_rm_postgresqlserver.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_postgresqlserver.py)
* [lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py)
* [lib/ansible/modules/cloud/azure/azure_rm_publicipaddress_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_resourcegroup.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_resourcegroup.py)
* [lib/ansible/modules/cloud/azure/azure_rm_resourcegroup_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_resourcegroup_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py)
* [lib/ansible/modules/cloud/azure/azure_rm_securitygroup_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_securitygroup_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_sqldatabase.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_sqldatabase.py)
* [lib/ansible/modules/cloud/azure/azure_rm_sqlserver.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_sqlserver.py)
* [lib/ansible/modules/cloud/azure/azure_rm_sqlserver_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_sqlserver_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_storageaccount.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_storageaccount.py)
* [lib/ansible/modules/cloud/azure/azure_rm_storageaccount_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_storageaccount_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_storageblob.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_storageblob.py)
* [lib/ansible/modules/cloud/azure/azure_rm_subnet.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_subnet.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_extension.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_extension.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualmachineimage_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualmachineimage_facts.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualnetwork.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualnetwork.py)
* [lib/ansible/modules/cloud/azure/azure_rm_virtualnetwork_facts.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/azure/azure_rm_virtualnetwork_facts.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @brusMX @devigned @haroldwongms @julienstroheker @lmazuel @nitzmahone @obsoleted @sozercan @tripdubroot @trstringer @tstringer @xscript @yaweiw @yuwzho @zikalino
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@zikalino will work on it.

Resolving patch:
diff --git a/contrib/inventory/azure_rm.py b/contrib/inventory/azure_rm.py
--- a/contrib/inventory/azure_rm.py
+++ b/contrib/inventory/azure_rm.py
@@ -50,6 +50,7 @@
  - ad_user
  - password
  - cloud_environment
+ - adfs_authority_url
 
 Environment variables:
  - AZURE_PROFILE
@@ -60,6 +61,7 @@
  - AZURE_AD_USER
  - AZURE_PASSWORD
  - AZURE_CLOUD_ENVIRONMENT
+ - AZURE_ADFS_AUTHORITY_URL
 
 Run for Specific Host
 -----------------------
@@ -209,6 +211,7 @@
 CLIError = None
 
 try:
+    from msrestazure.azure_active_directory import AADTokenCredentials
     from msrestazure.azure_exceptions import CloudError
     from msrestazure.azure_active_directory import MSIAuthentication
     from msrestazure import azure_cloud
@@ -219,6 +222,7 @@
     from azure.mgmt.resource.resources import ResourceManagementClient
     from azure.mgmt.resource.subscriptions import SubscriptionClient
     from azure.mgmt.compute import ComputeManagementClient
+    from adal.authentication_context import AuthenticationContext
 except ImportError as exc:
     HAS_AZURE_EXC = exc
     HAS_AZURE = False
@@ -245,6 +249,7 @@
     ad_user='AZURE_AD_USER',
     password='AZURE_PASSWORD',
     cloud_environment='AZURE_CLOUD_ENVIRONMENT',
+    adfs_authority_url='AZURE_ADFS_AUTHORITY_URL'
 )
 
 AZURE_CONFIG_SETTINGS = dict(
@@ -281,6 +286,8 @@ def __init__(self, args):
         self._compute_client = None
         self._resource_client = None
         self._network_client = None
+        self._adfs_authority_url = None
+        self._resource = None
 
         self.debug = False
         if args.debug:
@@ -316,6 +323,17 @@ def __init__(self, args):
         self.log("setting subscription_id")
         self.subscription_id = self.credentials['subscription_id']
 
+        # get authentication authority
+        # for adfs, user could pass in authority or not.
+        # for others, use default authority from cloud environment
+        if self.credentials.get('adfs_authority_url') is None:
+            self._adfs_authority_url = self._cloud_environment.endpoints.active_directory
+        else:
+            self._adfs_authority_url = self.credentials.get('adfs_authority_url')
+
+        # get resource from cloud environment
+        self._resource = self._cloud_environment.endpoints.active_directory_resource_id
+
         if self.credentials.get('credentials'):
             self.azure_credentials = self.credentials.get('credentials')
         elif self.credentials.get('client_id') and self.credentials.get('secret') and self.credentials.get('tenant'):
@@ -323,6 +341,20 @@ def __init__(self, args):
                                                                  secret=self.credentials['secret'],
                                                                  tenant=self.credentials['tenant'],
                                                                  cloud_environment=self._cloud_environment)
+
+        elif self.credentials.get('ad_user') is not None and \
+                self.credentials.get('password') is not None and \
+                self.credentials.get('client_id') is not None and \
+                self.credentials.get('tenant') is not None:
+
+                self.azure_credentials = self.acquire_token_with_username_password(
+                    self._adfs_authority_url,
+                    self._resource,
+                    self.credentials['ad_user'],
+                    self.credentials['password'],
+                    self.credentials['client_id'],
+                    self.credentials['tenant'])
+
         elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
             tenant = self.credentials.get('tenant')
             if not tenant:
@@ -331,9 +363,12 @@ def __init__(self, args):
                                                          self.credentials['password'],
                                                          tenant=tenant,
                                                          cloud_environment=self._cloud_environment)
+
         else:
             self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
-                      "Credentials must include client_id, secret and tenant or ad_user and password.")
+                      "Credentials must include client_id, secret and tenant or ad_user and password, or "
+                      "ad_user, password, client_id, tenant and adfs_authority_url(optional) for ADFS authentication, or "
+                      "be logged in using AzureCLI.")
 
     def log(self, msg):
         if self.debug:
@@ -453,6 +488,16 @@ def _get_credentials(self, params):
 
         return None
 
+    def acquire_token_with_username_password(self, authority, resource, username, password, client_id, tenant):
+        authority_uri = authority
+
+        if tenant is not None:
+            authority_uri = authority + '/' + tenant
+
+        context = AuthenticationContext(authority_uri)
+        token_response = context.acquire_token_with_username_password(resource, username, password, client_id)
+        return AADTokenCredentials(token_response)
+
     def _register(self, key):
         try:
             # We have to perform the one-time registration here. Otherwise, we receive an error the first
diff --git a/lib/ansible/module_utils/azure_rm_common.py b/lib/ansible/module_utils/azure_rm_common.py
--- a/lib/ansible/module_utils/azure_rm_common.py
+++ b/lib/ansible/module_utils/azure_rm_common.py
@@ -34,7 +34,8 @@
     password=dict(type='str', no_log=True),
     cloud_environment=dict(type='str'),
     cert_validation_mode=dict(type='str', choices=['validate', 'ignore']),
-    api_profile=dict(type='str', default='latest')
+    api_profile=dict(type='str', default='latest'),
+    adfs_authority_url=dict(type='str', default=None)
     # debug=dict(type='bool', default=False),
 )
 
@@ -48,6 +49,7 @@
     password='AZURE_PASSWORD',
     cloud_environment='AZURE_CLOUD_ENVIRONMENT',
     cert_validation_mode='AZURE_CERT_VALIDATION_MODE',
+    adfs_authority_url='AZURE_ADFS_AUTHORITY_URL'
 )
 
 # FUTURE: this should come from the SDK or an external location.
@@ -126,6 +128,7 @@
 
 try:
     from enum import Enum
+    from msrestazure.azure_active_directory import AADTokenCredentials
     from msrestazure.azure_exceptions import CloudError
     from msrestazure.azure_active_directory import MSIAuthentication
     from msrestazure.tools import resource_id, is_valid_resource_id
@@ -146,6 +149,7 @@
     from azure.mgmt.web import WebSiteManagementClient
     from azure.mgmt.containerservice import ContainerServiceClient
     from azure.storage.cloudstorageaccount import CloudStorageAccount
+    from adal.authentication_context import AuthenticationContext
 except ImportError as exc:
     HAS_AZURE_EXC = exc
     HAS_AZURE = False
@@ -262,6 +266,8 @@ def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
         self._dns_client = None
         self._web_client = None
         self._containerservice_client = None
+        self._adfs_authority_url = None
+        self._resource = None
 
         self.check_mode = self.module.check_mode
         self.api_profile = self.module.params.get('api_profile')
@@ -312,6 +318,17 @@ def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
         self.log("setting subscription_id")
         self.subscription_id = self.credentials['subscription_id']
 
+        # get authentication authority
+        # for adfs, user could pass in authority or not.
+        # for others, use default authority from cloud environment
+        if self.credentials.get('adfs_authority_url') is None:
+            self._adfs_authority_url = self._cloud_environment.endpoints.active_directory
+        else:
+            self._adfs_authority_url = self.credentials.get('adfs_authority_url')
+
+        # get resource from cloud environment
+        self._resource = self._cloud_environment.endpoints.active_directory_resource_id
+
         if self.credentials.get('credentials') is not None:
             # AzureCLI credentials
             self.azure_credentials = self.credentials['credentials']
@@ -324,6 +341,19 @@ def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
                                                                      cloud_environment=self._cloud_environment,
                                                                      verify=self._cert_validation_mode == 'validate')
 
+        elif self.credentials.get('ad_user') is not None and \
+                self.credentials.get('password') is not None and \
+                self.credentials.get('client_id') is not None and \
+                self.credentials.get('tenant') is not None:
+
+                self.azure_credentials = self.acquire_token_with_username_password(
+                    self._adfs_authority_url,
+                    self._resource,
+                    self.credentials['ad_user'],
+                    self.credentials['password'],
+                    self.credentials['client_id'],
+                    self.credentials['tenant'])
+
         elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
             tenant = self.credentials.get('tenant')
             if not tenant:
@@ -336,8 +366,9 @@ def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
                                                          verify=self._cert_validation_mode == 'validate')
         else:
             self.fail("Failed to authenticate with provided credentials. Some attributes were missing. "
-                      "Credentials must include client_id, secret and tenant or ad_user and password or "
-                      "be logged using AzureCLI.")
+                      "Credentials must include client_id, secret and tenant or ad_user and password, or "
+                      "ad_user, password, client_id, tenant and adfs_authority_url(optional) for ADFS authentication, or "
+                      "be logged in using AzureCLI.")
 
         # common parameter validation
         if self.module.params.get('tags'):
@@ -347,6 +378,17 @@ def __init__(self, derived_arg_spec, bypass_checks=False, no_log=False,
             res = self.exec_module(**self.module.params)
             self.module.exit_json(**res)
 
+    def acquire_token_with_username_password(self, authority, resource, username, password, client_id, tenant):
+        authority_uri = authority
+
+        if tenant is not None:
+            authority_uri = authority + '/' + tenant
+
+        context = AuthenticationContext(authority_uri)
+        token_response = context.acquire_token_with_username_password(resource, username, password, client_id)
+
+        return AADTokenCredentials(token_response)
+
     def check_client_version(self, client_type):
         # Ensure Azure modules are at least 2.0.0rc5.
         package_version = AZURE_PKG_VERSIONS.get(client_type.__name__, None)
diff --git a/lib/ansible/utils/module_docs_fragments/azure.py b/lib/ansible/utils/module_docs_fragments/azure.py
--- a/lib/ansible/utils/module_docs_fragments/azure.py
+++ b/lib/ansible/utils/module_docs_fragments/azure.py
@@ -54,6 +54,12 @@ class ModuleDocFragment(object):
               the C(AZURE_CLOUD_ENVIRONMENT) environment variable.
         default: AzureCloud
         version_added: 2.4
+    adfs_authority_url:
+        description:
+            - Azure AD authority url. Use when authenticating with Username/password, and has your own ADFS authority.
+        required: false
+        default: null
+        version_added: 2.6
     cert_validation_mode:
         description:
             - Controls the certificate validation behavior for Azure endpoints. By default, all modules will validate the server certificate, but


Test patch: