Members.psm1

# https://docs.gitlab.com/ee/api/members.html#valid-access-levels
function Get-GitlabMemberAccessLevel {

    param(
        [Parameter(Position=0)]
        [string]
        $AccessLevel
    )

    $Levels = [PSCustomObject]@{
        NoAccess = 0
        MinimalAccess = 5
        Guest = 10
        Reporter = 20
        Developer = 30
        Maintainer = 40
        Owner = 50
        Admin = 60
    }

    if ($AccessLevel) {
        $Levels.$AccessLevel
    } else {
        $Levels
    }
}

function Get-GitlabMembershipSortKey {
    param(
    )

    @(
        @{
            Expression = 'AccessLevel'
            Descending = $true
        },
        @{
            Expression = 'Username'
            Descending = $false
        }
    )
}

# https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project
function Get-GitlabGroupMember {
    param (
        [Parameter(Position=0, ValueFromPipelineByPropertyName)]
        [string]
        $GroupId = '.',

        [Parameter()]
        [string]
        $UserId,

        [switch]
        [Parameter()]
        $All,

        [Parameter()]
        [string]
        [ValidateSet('guest', 'reporter', 'developer', 'maintainer', 'owner')]
        $MinAccessLevel,

        [Parameter()]
        [int]
        $MaxPages = 10,

        [Parameter()]
        [string]
        $SiteUrl
    )

    $Group = Get-GitlabGroup -GroupId $GroupId -SiteUrl $SiteUrl
    if ($UserId) {
        $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    }

    $Members = $All ? "members/all" : "members"
    $Resource = $User ? "groups/$($Group.Id)/$Members/$($User.Id)" : "groups/$($Group.Id)/$Members"

    $Members = Invoke-GitlabApi GET $Resource -MaxPages $MaxPages -SiteUrl $SiteUrl
    if ($MinAccessLevel) {
        $MinAccessLevelLiteral = Get-GitlabMemberAccessLevel $MinAccessLevel
        $Members = $Members | Where-Object access_level -ge $MinAccessLevelLiteral
    }

    $Members | New-WrapperObject 'Gitlab.Member' |
        Add-Member -PassThru -NotePropertyMembers @{
            GroupId = $Group.Id
        } |
        Sort-Object -Property $(Get-GitlabMembershipSortKey)
}

# https://docs.gitlab.com/ee/api/members.html#add-a-member-to-a-group-or-project
function Add-GitlabGroupMember {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $GroupId,

        [Parameter(Mandatory)]
        [string]
        $UserId,

        [Parameter(Mandatory)]
        [ValidateSet('guest', 'reporter', 'developer', 'maintainer')]
        [string]
        $AccessLevel,

        [Parameter(Mandatory=$false)]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    $Group = Get-GitlabGroup -GroupId $GroupId -SiteUrl $SiteUrl

    $Request = @{
        user_id = $User.Id
        access_level = Get-GitlabMemberAccessLevel $AccessLevel
    }

    if ($PSCmdlet.ShouldProcess($Group.FullName, "grant $($User.Username) '$AccessLevel' membership")) {
        Invoke-GitlabApi POST "groups/$($Group.Id)/members" -Body $Request -SiteUrl $SiteUrl |
            New-WrapperObject 'Gitlab.Member'
    }
}

function Remove-GitlabGroupMember {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $GroupId,
        
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('Username')]
        [string]
        $UserId,
        
        [Parameter()]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    $Group = Get-GitlabGroup -GroupId $GroupId -SiteUrl $SiteUrl
        
    if ($PSCmdlet.ShouldProcess($Group.FullName, "remove $($User.Username)'s group membership")) {
        try {
            # https://docs.gitlab.com/ee/api/members.html#remove-a-member-from-a-group-or-project
            Invoke-GitlabApi DELETE "groups/$($Group.Id)/members/$($User.Id)" -SiteUrl $SiteUrl -WhatIf:$WhatIf | Out-Null
            Write-Host "Removed $($User.Username) from $($Group.Name)"
        }
        catch {
            Write-Error "Error removing $($User.Username) from $($Group.Name): $_"
        }
    }
}

function Get-GitlabProjectMember {
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $ProjectId = '.',
        
        [Parameter()]
        [Alias('Username')]
        [string]
        $UserId,
        
        [switch]
        [Parameter()]
        $All,
        
        [Parameter()]
        [int]
        $MaxPages = 10,
        
        [Parameter()]
        [string]
        $SiteUrl
    )

    $Project = Get-GitlabProject -ProjectId $ProjectId -SiteUrl $SiteUrl

    if ($UserId) {
        $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    }

    $Members = $All ? "members/all" : "members"
    $Resource = $User ? "projects/$($Project.Id)/$Members/$($User.Id)" : "projects/$($Project.Id)/$Members"

    # https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project
    Invoke-GitlabApi GET $Resource -MaxPages $MaxPages -SiteUrl $SiteUrl |
        New-WrapperObject 'Gitlab.Member' |
        Add-Member -PassThru -NotePropertyMembers @{
            ProjectId = $Project.Id
        } |
        Sort-Object -Property $(Get-GitlabMembershipSortKey)
}

function Set-GitlabProjectMember {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$false)]
        [string]
        $ProjectId = '.',

        [Parameter(Position=0, Mandatory=$true)]
        [Alias('Username')]
        [string]
        $UserId,

        [Parameter(Position=1, Mandatory=$true)]
        [ValidateSet('guest', 'reporter', 'developer', 'maintainer', 'owner')]
        [string]
        $AccessLevel,

        [Parameter(Mandatory=$false)]
        [string]
        $SiteUrl
    )

    $Existing = $Null
    try {
        $Existing = Get-GitlabProjectMember -ProjectId @ProjectId -UserId $UserId -SiteUrl $SiteUrl
    }
    catch {
        Write-Verbose "User '$UserId' is not a member of '$ProjectId'"
    }

    if ($Existing) {
        # https://docs.gitlab.com/ee/api/members.html#edit-a-member-of-a-group-or-project
        $Request = @{
            HttpMethod = 'PUT'
            Path       = "projects/$($Existing.ProjectId)/members/$($Existing.Id)"
            Body      = @{
                access_level = Get-GitlabMemberAccessLevel $AccessLevel
            }
            SiteUrl = $SiteUrl
        }
        if ($PSCmdlet.ShouldProcess("Project '$ProjectId'", "update '$($Existing.Name)' membership to '$AccessLevel'")) {
            Invoke-GitlabApi @Request | New-WrapperObject 'Gitlab.Member'
        }
    } else {
        if ($PSCmdlet.ShouldProcess("Project '$ProjectId'", "add '$UserId' as '$AccessLevel'")) {
            Add-GitlabProjectMember -ProjectId $ProjectId -UserId $UserId -AccessLevel $AccessLevel -SiteUrl $SiteUrl
        }
    }
}

function Add-GitlabProjectMember {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory=$false)]
        [string]
        $ProjectId = '.',

        [Parameter(Position=0, Mandatory=$true)]
        [Alias('Username')]
        [string]
        $UserId,

        [Parameter(Position=1, Mandatory=$true)]
        [ValidateSet('guest', 'reporter', 'developer', 'maintainer', 'owner')]
        [string]
        $AccessLevel,

        [Parameter(Mandatory=$false)]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    $Project = Get-GitlabProject -ProjectId $ProjectId -SiteUrl $SiteUrl

    $Request = @{
        # https://docs.gitlab.com/ee/api/members.html#add-a-member-to-a-group-or-project
        HttpMethod = 'POST'
        Path       = "projects/$($Project.Id)/members"
        Body       = @{
            user_id      = $User.Id
            access_level = Get-GitlabMemberAccessLevel $AccessLevel
        }
        SiteUrl = $SiteUrl
    }

    if ($PSCmdlet.ShouldProcess($Project.PathWithNamespace, "grant '$($User.Username)' $AccessLevel membership")) {
        Invoke-GitlabApi @Request | New-WrapperObject 'Gitlab.Member'
    }
}

# https://docs.gitlab.com/ee/api/members.html#remove-a-member-from-a-group-or-project
function Remove-GitlabProjectMember {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]
        $ProjectId = '.',

        [Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName)]
        [Alias('Username')]
        [string]
        $UserId,

        [Parameter()]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -UserId $UserId -SiteUrl $SiteUrl
    $Project = Get-GitlabProject -ProjectId $ProjectId -SiteUrl $SiteUrl

    if ($PSCmdlet.ShouldProcess("$($Project.PathWithNamespace)", "Remove $($User.Username)'s membership")) {
        if ($Project.Owner.Username -eq $User.Username) {
            Write-Warning "Can't remove owner '$($User.Username)' from '$($Project.PathWithNamespace)'"
        } else {
            try {
                Invoke-GitlabApi DELETE "projects/$($Project.Id)/members/$($User.Id)" -SiteUrl $SiteUrl | Out-Null
                Write-Host "Removed $($User.Username) from $($Project.Name)"
            }
            catch {
                Write-Error "Error removing $($User.Username) from $($Project.Name): $_"
            }
        }
    }
}

# https://docs.gitlab.com/ee/api/users.html#user-memberships-admin-only
function Get-GitlabUserMembership {
    [CmdletBinding(DefaultParameterSetName='ByUsername')]
    param (
        [Parameter(ParameterSetName='ByUsername', Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [string]
        $Username,

        [Parameter(ParameterSetName='Me')]
        [switch]
        $Me,

        [Parameter]
        [string]
        $SiteUrl,

        [switch]
        [Parameter(Mandatory=$false)]
        $WhatIf
    )

    if ($Me) {
        $Username = $(Get-GitlabUser -Me).Username
    }

    $User = Get-GitlabUser -Username $Username -SiteUrl $SiteUrl

    Invoke-GitlabApi GET "users/$($User.Id)/memberships" -MaxPages 10 -SiteUrl $SiteUrl -WhatIf:$WhatIf |
        New-WrapperObject 'Gitlab.UserMembership'
}

function Remove-GitlabUserMembership {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')]
    param(
        [Parameter(Mandatory, Position=0, ValueFromPipelineByPropertyName)]
        [string]
        $Username,

        [Parameter()]
        $Group,

        [Parameter()]
        $Project,

        [Parameter()]
        [switch]
        $RemoveAllAccess,

        [Parameter]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -Username $Username -SiteUrl $SiteUrl

    if ($Group) {
        if ($PSCmdlet.ShouldProcess("$($Group -join ',' )", "remove $Username access from groups")) {
            $Group | ForEach-Object {
                $User | Remove-GitlabGroupMember -GroupId $_ -SiteUrl $SiteUrl
            }
        }
    }
    if ($Project) {
        if ($PSCmdlet.ShouldProcess("$($Project -join ',' )", "remove $Username access from project ")) {
            $Project | ForEach-Object {
                $User | Remove-GitlabProjectMember -ProjectId $_ -SiteUrl $SiteUrl
            }
        }
    }
    if ($RemoveAllAccess) {
        $CurrentAccess = $User | Get-GitlabUserMembership
        $Request = @{
            Group   = $CurrentAccess | Where-Object Sourcetype -eq 'Namespace' | Select-Object -ExpandProperty SourceId
            Project = $CurrentAccess | Where-Object Sourcetype -eq 'Project' | Select-Object -ExpandProperty SourceId
            SiteUrl = $SiteUrl
        }
        $User | Remove-GitlabUserMembership @Request
    }
}

# https://docs.gitlab.com/ee/api/members.html#add-a-member-to-a-group-or-project
function Add-GitlabUserMembership {
    [CmdletBinding()]
    param (
        [Parameter(Position=0, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [string]
        $Username,

        [Parameter(Position=1, Mandatory=$true)]
        [string]
        $GroupId,

        [Parameter(Position=2, Mandatory=$true)]
        [string]
        [ValidateSet('developer', 'maintainer', 'owner')]
        $AccessLevel,

        [Parameter(Mandatory=$false)]
        [string]
        $SiteUrl,

        [Parameter()]
        [switch]
        $WhatIf
    )

    $Group = Get-GitlabGroup -GroupId $GroupId
    $User = Get-GitlabUser -UserId $Username

    Invoke-GitlabApi POST "groups/$($Group.Id)/members" @{
        user_id = $User.Id
        access_level = Get-GitlabMemberAccessLevel $AccessLevel
    }  -SiteUrl $SiteUrl -WhatIf:$WhatIf | Out-Null
    Write-Host "$($User.Username) added to $($Group.FullPath)"
}

# https://docs.gitlab.com/ee/api/members.html#edit-a-member-of-a-group-or-project
function Update-GitlabUserMembership {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName='Group')]
    param (
        [Parameter(Position=0, Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $Username,

        [Parameter(ParameterSetName='Group', Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $GroupId,

        [Parameter(ParameterSetName='Project', Mandatory, ValueFromPipelineByPropertyName)]
        [string]
        $ProjectId,

        [Parameter(Mandatory)]
        [string]
        [ValidateSet('developer', 'maintainer', 'owner')]
        $AccessLevel,

        [Parameter()]
        [string]
        $SiteUrl
    )

    $User = Get-GitlabUser -UserId $Username

    $Rows = @()

    $AccessLevelLiteral = Get-GitlabMemberAccessLevel $AccessLevel

    switch ($PSCmdlet.ParameterSetName) {
        Group {
            $Group = Get-GitlabGroup -GroupId $GroupId
            if ($PSCmdLet.ShouldProcess($Group.FullName, "update $($User.Username)'s membership access level to '$AccessLevel' on group")) {
                $Rows = Invoke-GitlabApi PUT "groups/$($Group.Id)/members/$($User.Id)" @{
                    access_level = $AccessLevelLiteral
                } -SiteUrl $SiteUrl
            }
         }
        Project {
            $Project = Get-GitlabProject -ProjectId $ProjectId
            if ($PSCmdLet.ShouldProcess($Project.PathWithNamespace, "update $($User.Username)'s membership access level to '$AccessLevel' on project")) {
                $Rows = Invoke-GitlabApi PUT "projects/$($Project.Id)/members/$($User.Id)" @{
                    access_level = $AccessLevelLiteral
                }  -SiteUrl $SiteUrl
            }
        }
    }

    $Rows | New-WrapperObject 'Gitlab.Member'
}