Skip to content

Commit

Permalink
Merge pull request #446 from SEKOIA-IO/Refactor/Azure_ad
Browse files Browse the repository at this point in the history
Add some actions and update others
  • Loading branch information
squioc authored Nov 2, 2023
2 parents 1c87833 + 09018da commit 2b919cc
Show file tree
Hide file tree
Showing 14 changed files with 2,168 additions and 634 deletions.
15 changes: 15 additions & 0 deletions AzureActiveDirectory/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## 2023-10-31 - 2.5

### Changed

- Add 3 actions : Delete app, Revoke sign in, Reset password
- Update 5 actions : Disable User, Enable user, Get sign in, Get User, Get User authentication methods
18 changes: 18 additions & 0 deletions AzureActiveDirectory/action_delete_app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "Delete app",
"description": "Delete an app in azure AD. Requires the Application.ReadWrite.OwnedBy or Application.ReadWrite.All.",
"uuid": "ec4039ec-7991-48ac-9d8f-503ef17013a2",
"docker_parameters": "DeleteApplicationAction",
"arguments": {
"title": "ApplicationArguments",
"type": "object",
"properties": {
"id": {
"title": "Id",
"description": "ID of the app.",
"type": "string"
}
}
},
"results": {}
}
28 changes: 28 additions & 0 deletions AzureActiveDirectory/action_reset_user_password.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "Reset User Password",
"description": "Reset a user's password. You will need UserAuthenticationMethod.ReadWrite.All deleguated permission. And to disable the MFA authentication in your azure AD",
"uuid": "a6676d23-4b6d-4892-95c7-d02cc8c9436d",
"docker_parameters": "ResetUserPasswordAction",
"arguments": {
"title": "RequiredTwoUserArguments",
"type": "object",
"properties": {
"id": {
"title": "Id",
"description": "ID of the user. id or userPrincipalName should be specified.",
"type": "string"
},
"userPrincipalName": {
"title": "Userprincipalname",
"description": "Principal Name of the user. id or userPrincipalName should be specified.",
"type": "string"
},
"userNewPassword": {
"title": "Usernewpassword",
"description": "New password, required to reset the old one of course.",
"type": "string"
}
}
},
"results": {}
}
23 changes: 23 additions & 0 deletions AzureActiveDirectory/action_revoke_signin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Revoke Sign in",
"description": "Invalidates all the refresh tokens issued to applications for a user. Requires the User.ReadWrite.All or Directory.ReadWrite.All permissions.",
"uuid": "4be1e1e1-cb41-4b93-ac3d-cdc6a5c38c71",
"docker_parameters": "RevokeSignInsSessionsAction",
"arguments": {
"title": "SingleUserArguments",
"type": "object",
"properties": {
"id": {
"title": "Id",
"description": "ID of the app.",
"type": "string"
},
"userPrincipalName": {
"title": "Userprincipalname",
"description": "Principal Name of the user. id or userPrincipalName should be specified.",
"type": "string"
}
}
},
"results": {}
}
47 changes: 42 additions & 5 deletions AzureActiveDirectory/azure_ad/base.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from functools import cached_property

from azure.identity import ClientSecretCredential
from msgraph.core import APIVersion, GraphClient
from azure.identity.aio import ClientSecretCredential # async credentials only
from azure.identity import UsernamePasswordCredential
from kiota_authentication_azure.azure_identity_authentication_provider import AzureIdentityAuthenticationProvider
from msgraph import GraphRequestAdapter, GraphServiceClient
from pydantic import BaseModel, Field, root_validator
from sekoia_automation.action import Action
from sekoia_automation.module import Module


class AzureADConfiguration(BaseModel):
tenant_id: str = Field(..., description="ID of the Azure AD tenant")
username: str = Field(..., description="")
password: str = Field(..., description="")
client_id: str = Field(
...,
description="Client ID. An application needs to be created in the Azure Portal and assigned relevent permissions. Its Client ID should then be used in this configuration.", # noqa: E501
Expand All @@ -33,11 +37,30 @@ def client(self):
client_id=self.module.configuration.client_id,
client_secret=self.module.configuration.client_secret,
)
return GraphClient(
credential=credentials,
api_version=APIVersion.beta,
auth_provider = AzureIdentityAuthenticationProvider(credentials)
adapter = GraphRequestAdapter(auth_provider)

return GraphServiceClient(request_adapter=adapter)

@cached_property
def delegated_client(self):
"""
Used for password reset action
It's a not a good practice to use. but we app permission
not supported for this action
"""
credentials = UsernamePasswordCredential(
client_id=self.module.configuration.client_id,
username=self.module.configuration.username,
password=self.module.configuration.password,
)

return GraphServiceClient(credentials=credentials)


class ApplicationArguments(BaseModel):
id: str | None = Field(None, description="ID of the user. id or userPrincipalName should be specified.")


class SingleUserArguments(BaseModel):
id: str | None = Field(None, description="ID of the user. id or userPrincipalName should be specified.")
Expand All @@ -54,3 +77,17 @@ def validate_id_or_userPrincipalName(cls, values):
raise ValueError("'id' or 'userPrincipalName' should be specified")

return values


class RequiredTwoUserArguments(SingleUserArguments):
userNewPassword: str | None = Field(
None,
description="New password, required to reset the old one of course.",
)

@root_validator
def validate_two_arguments(cls, values):
if not ((values.get("id") or values.get("userPrincipalName")) and values.get("userNewPassword")):
raise ValueError("'userPrincipalName' and ('id' or 'userPrincipalName') should be specified")

return values
26 changes: 26 additions & 0 deletions AzureActiveDirectory/azure_ad/delete_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import asyncio

from .base import MicrosoftGraphAction, ApplicationArguments

from kiota_abstractions.native_response_handler import NativeResponseHandler
from kiota_http.middleware.options import ResponseHandlerOption
from msgraph.generated.users.item.messages.messages_request_builder import MessagesRequestBuilder


class DeleteApplicationAction(MicrosoftGraphAction):
name = "Delete application"
description = (
"Delete an application object. Requires the Application.ReadWrite.OwnedBy or Application.ReadWrite.All."
)

async def query_delete_app(self, id, req_conf):
return await self.client.applications.by_application_id(id).delete(request_configuration=req_conf)

async def run(self, arguments: ApplicationArguments):
request_configuration = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
options=[ResponseHandlerOption(NativeResponseHandler())],
)

response = await self.query_delete_app(arguments.id, request_configuration)

response.raise_for_status()
51 changes: 42 additions & 9 deletions AzureActiveDirectory/azure_ad/get_sign_ins.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from pydantic import BaseModel
import asyncio

from .base import MicrosoftGraphAction, SingleUserArguments

from kiota_abstractions.native_response_handler import NativeResponseHandler
from kiota_http.middleware.options import ResponseHandlerOption
from msgraph.generated.users.item.messages.messages_request_builder import MessagesRequestBuilder


class DeviceDetail(BaseModel):
browser: str | None
Expand Down Expand Up @@ -100,21 +105,49 @@ class GetSignInsResults(BaseModel):

class GetSignInsAction(MicrosoftGraphAction):
name = "Get SignIns"
description = "Get the last sign ins of an Azure AD user. Requires the AuditLog.Read.All and Directory.Read.All permissions." # noqa: E501
results_model = GetSignInsResults
description = "Get the last sign ins of an Azure AD user. Requires the AuditLog.Read.All and Directory.Read.All app permissions." # noqa: E501
# results_model doesn't support async model
# results_model = GetSignInsResults

def run(self, arguments: SingleUserArguments):
params = {}
async def query_get_user_signin(self, req_conf):
return await self.client.audit_logs.sign_ins.get(request_configuration=req_conf)

async def run(self, arguments: SingleUserArguments):
if arguments.id:
params["$filter"] = f"userId eq '{arguments.id}'"
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
filter=f"userId eq '{arguments.id}'"
)
elif arguments.userPrincipalName:
params["$filter"] = f"userPrincipalName eq '{arguments.userPrincipalName}'"
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
filter=f"userPrincipalName eq '{arguments.userPrincipalName}'"
)

response = self.client.get(
"/auditLogs/signIns",
params=params,
request_configuration = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
options=[ResponseHandlerOption(NativeResponseHandler())], query_parameters=query_params
)

response = await self.query_get_user_signin(request_configuration)

response.raise_for_status()

return {"signIns": response.json()["value"]}


class RevokeSignInsSessionsAction(MicrosoftGraphAction):
name = "Revoke SignIns Sessions"
description = "Invalidates all the refresh tokens issued to applications for a user. Requires the User.ReadWrite.All or Directory.ReadWrite.All permissions." # noqa: E501

async def query_revoke_signin(self, signIn_param, req_conf):
return await self.client.users.by_user_id(signIn_param).revoke_sign_in_sessions.post(
request_configuration=req_conf
)

async def run(self, arguments: SingleUserArguments):
request_configuration = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
options=[ResponseHandlerOption(NativeResponseHandler())],
)
signIn_param = arguments.id or arguments.userPrincipalName

response = await self.query_revoke_signin(signIn_param, request_configuration)

response.raise_for_status()
34 changes: 24 additions & 10 deletions AzureActiveDirectory/azure_ad/get_user_authentication_methods.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from pydantic import BaseModel
import asyncio

from .base import MicrosoftGraphAction, RequiredSingleUserArguments

from kiota_abstractions.native_response_handler import NativeResponseHandler
from kiota_http.middleware.options import ResponseHandlerOption
from msgraph.generated.users.item.messages.messages_request_builder import MessagesRequestBuilder


class GetUserAuthenticationMethodsResults(BaseModel):
id: str
Expand All @@ -20,20 +25,29 @@ class GetUserAuthenticationMethodsResults(BaseModel):
class GetUserAuthenticationMethodsAction(MicrosoftGraphAction):
name = "Get User Authentication Methods"
description = "Get information about an user's authentication methods (such as their MFA status). Requires the UserAuthenticationMethod.Read.All permission." # noqa: E501
results_model = GetUserAuthenticationMethodsResults
# results_model = GetUserAuthenticationMethodsResults

def run(self, arguments: RequiredSingleUserArguments):
params = {}
async def query_user_auth_methods(self, req_conf):
return await self.client.reports.authentication_methods.user_registration_details.get(
request_configuration=req_conf
)

async def run(self, arguments: RequiredSingleUserArguments):
if arguments.id:
params["$filter"] = f"id eq '{arguments.id}'"
else:
params["$filter"] = f"userPrincipalName eq '{arguments.userPrincipalName}'"

response = self.client.get(
"/reports/authenticationMethods/userRegistrationDetails/",
params=params,
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
filter=f"id eq '{arguments.id}'"
)
elif arguments.userPrincipalName:
query_params = MessagesRequestBuilder.MessagesRequestBuilderGetQueryParameters(
filter=f"userPrincipalName eq '{arguments.userPrincipalName}'"
)

request_configuration = MessagesRequestBuilder.MessagesRequestBuilderGetRequestConfiguration(
options=[ResponseHandlerOption(NativeResponseHandler())], query_parameters=query_params
)

response = await self.query_user_auth_methods(request_configuration)

response.raise_for_status()

return response.json()["value"][0]
Loading

0 comments on commit 2b919cc

Please sign in to comment.