【沉浸式解决问题】通过GitLab API批量创建删除用户和项目

前言

之前发了一篇GitLab被恶意注册,所以关闭注册功能,被恶意创建了很多用户和项目,当时未找到批量删除的方法,后续操作用户和项目实在是不方便,得找半天,通过搜索,找到了可以通过GitLab API批量创建删除用户和项目。

这里直接介绍解决教程,有关GitLab api的讲解以后单独写一篇。

一、原理说明

GitLab官方提供了RESTful API,可以通过接口的方式管理GitLab。
官方文档提供了非常详细的说明文档,API部分有具体的参数说明,使用curl访问示例,json返回结果示例。
REST API:https://docs.gitlab.com/api/rest/
Projects API:https://docs.gitlab.com/api/projects/
Users API:https://docs.gitlab.com/api/users/
项目可见性分为私有、内部、公共,用户权限分为普通和管理员,除了访问公共项目,其他的访问都需要一个用户的令牌,普通用户可以查询,管理员用户可以增删改查。
所以流程就很简单了,登录一个管理员账号,创建个人token令牌,使用任意一种语言或工具,带着令牌访问REST API,查询要删除的用户列表,再遍历删除,用户创建的项目会同步删除。

二、创建访问令牌

登录管理员账号,点击右上角个人中心,点击设置
在这里插入图片描述
点击左侧的Access Tokens设置个人令牌,name随便写一个,Expires at是过期时间,可以不写,再下面的Scopes是权限范围,必须选一个,由于我们需要删除用户,勾选api和read_user权限,最后点击创建按钮
在这里插入图片描述
创建完成后,会提示创建成功,以及Token值,注意下面的提示:
Make sure you save it - you won’t be able to access it again.
意思是,确保你保存了它——你将无法再次访问它,点击右边的复制按钮,发送到微信或者是保存到txt文件,以防忘记,不过忘了再创建一个就好了
在这里插入图片描述
往下可以看到激活的个人令牌,点击Revoke可以撤销制定令牌
在这里插入图片描述
一开始以为是sudo权限,在ApiPost种测试了一下不行,提示需要read_user和api权限
在这里插入图片描述
说明一下其他几个选项,感觉read_user和sudo描述不准确
在这里插入图片描述

权限名称权限范围
api对所有的Groups(组)和Projects(项目)的读/写权限
read_user对所有用户信息的只读权限
sudo管理员权限,包括所有操作
read_repository通过Git-over-HTTP对私有项目的只读权限

三、批量删除用户和项目

实现方式可以选择在命令行中使用curl,或者使用python、Java、js等语言,我比较推荐使用python,方便编写和处理查询列表,也便于保存,如果没有的话使用命令行就行,毕竟是一次性的操作。
打开PyCharm,创建新项目
在这里插入图片描述
创建后如下图,右键项目名称,创建一个子项目目录
在这里插入图片描述
就叫批量删除用户吧,然后回车
在这里插入图片描述
右键子目录,创建一个python文件
在这里插入图片描述
也叫批量删除用户,然后回车
在这里插入图片描述
由于没有什么复杂的操作,所以只需要安装一个requests库,点击下面的Termianl,输入pip install requests,之前将下载源换成了阿里的,这里就不讲怎么换了
在这里插入图片描述
处理代码如下,注释很详细,下面代码有问题,后续会说,不要复制这个

import requests

# 配置GitLab地址
GITLAB_URL = "http://10.10.10.10"
# 配置Token令牌
PRIVATE_TOKEN = "_dmeNrhyNymzbvSyNj6j"

# 这里用删除2024年1月1日以后创建的用户示范,还有多种查询参数,有特殊要求可以在查询结果中再筛选
def main():
    # Token令牌要放在header中,在这里统一配置,参数名必须大写
    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN}

    # 第一步:查询2024-01-01之后创建的所用用户,分页大小为100
    response_total = requests.get(
        f"{GITLAB_URL}/api/v4/users",
        # 查询时Token要放在header中
        headers = headers,
        params = {
            "created_after": "2024-01-01",
            "per_page": 100
        }
    )
    # 判断所用用户查询是否成功
    if response_total.status_code != 200:
        print(f"所有用户查询失败: {response_total.status_code}{response_total.text}")
        return
    # 获取需处理总用户数和总页数
    total_users = int(response_total.headers.get('X-Total', 1))
    total_pages = int(response_total.headers.get('X-Total-Pages', 1))
    print(f"总用户数: {total_users},总页数: {total_pages}")

    # 第二步:遍历分页
    for page in range(1, total_pages + 1):
        print(f"正在处理第 {page}/{total_pages} 页")
        # 在全部查询中加上分页号的参数
        response_users = requests.get(
            f"{GITLAB_URL}/api/v4/users",
            headers = headers,
            params = {
                "created_after": "2024-01-01",
                "per_page": 100,
                "page": page
            }
        )
        # 判断分页查询是否成功
        if response_users.status_code != 200:
            print(f"获取第页{page}用户失败: {response_users.status_code}{response_users.text}")
            continue

        # 第三步:批量删除当前页用户,遍历当前分页的每一个用户,这里可以再额外筛选
        for user in response_users.json():
            response_delete = requests.delete(
                f"{GITLAB_URL}/api/v4/users/{user['id']}",
                headers = headers
                # 删除用户时Token也可以放在参数里,但是参数名必须小写,在ApiPost中只能在参数小写,header中大小写都不行,奇怪
                # params = {"private_token": PRIVATE_TOKEN}
            )
            # 删除用户成功则返回204且无内容
            if response_delete.status_code == 204:
            	# 如果是删除几万条用户就不用打印正常日志了
                print(f"已删除用户: {user['username']}")
            else:
                print(f"删除失败 id:{user['id']} {user['username']},[{response_delete.status_code}]:{response_delete.text}")

if __name__ == "__main__":
    main()

点击这个绿色的小三角运行程序
在这里插入图片描述
日志如下
在这里插入图片描述
第一个问题,很低级的错误,删除数据经典错误,又不小心写出来了,留着鞭策自己
删除数据要从最后一页或一行开始删除,因为删除了第一页,之前的序号相当于都往前一个,此时再删除第二页,删除的其实是原来的第三页,原来的第二页就被跳过了,理论上每次都会跳过一页,所以只会删除一半,这里能到第18页是因为第二个问题。
解决方案有三种,一种是使用while,循环查第一页,直到没有数据,第二种是从最后一页开始删除,再往前遍历,还有一种省心的,提前创建一个数组,把每次查询到的user收集起来,最后再遍历数组删除,会多花费一点内存
数据量不大选第三种最好,这里我会选择第一种,更加通用一些
在这里插入图片描述
第二个问题,GitLab在删除用户时,如果用户有关联信息,则会执行soft deleted,即软删除,会把该用户的相关信息,移动到一个叫Ghost User的用户下
在这里插入图片描述
在用户管理处,正常应该在2FA Disabled里面,搜索ghost,就可以看到这个用户
在这里插入图片描述
解决方案是加一个hard_delete参数,值设为true,可以看到默认为soft deleted,
hard_delete的描述翻译:如果为真,则通常会被移动到 Ghost 用户的贡献将被删除,同时也会删除该用户拥有的所有组。
在这里插入图片描述
第三个问题,可以看到虽然用户删除到了770,但是组也增加到了804,可能是GitLab的暂存机制吧,这里增加的组是暂时的,过一会儿就好了
在这里插入图片描述
在这里插入图片描述
重来一遍,加上hard_delete参数,修改第三步删除用户的请求,删除正常日志

        # 第三步:批量删除当前页用户,遍历当前分页的每一个用户,这里可以再额外筛选
        for user in response_users.json():
            response_delete = requests.delete(
                f"{GITLAB_URL}/api/v4/users/{user['id']}",
                headers = headers,
                # 删除用户时Token也可以放在参数里,但是参数名必须小写,在ApiPost中只能在参数小写,header中大小写都不行,奇怪
                # params = {"private_token": PRIVATE_TOKEN}
                params = {"hard_delete": "true"}
            )
            # 删除用户成功则返回204且无内容
            if response_delete.status_code != 204:
                print(f"删除失败 id:{user['id']} {user['username']},[{response_delete.status_code}]:{response_delete.text}")

由于没有打印正常日志,看不出来,我看着控制台可以看出来1234页是慢慢删除的,5678是一秒出来的,应该还剩300多个还可以再测试一次
在这里插入图片描述
删除的数量差是380,groups增加了435,奇怪
在这里插入图片描述
几分钟的功夫,还退回来20个用户,groups少了64,先不管了
在这里插入图片描述

在ApiPost中测试一个超过4的页数,可以看到返回结果是一个"[]"
在这里插入图片描述
在分页查询结果处加个判断,打印日志并跳过

        # 判断分页查询是否成功
        if response_users.status_code != 200:
            print(f"获取第页{page}用户失败: {response_users.status_code}{response_users.text}")
            continue
        elif response_users.text == "[]":
            print(f"获取第页{page}用户失败: 无数据")
            continue

不错,这次终于解决删除问题了
在这里插入图片描述
按理说删除200个,user少了241,groups少了200,怀疑是GitLab的统计接口的问题
在这里插入图片描述
ApiPost中查询还剩下112个,正确无误
在这里插入图片描述

四、最终python代码

改写查询逻辑,不通过分页页数查询,采用while循环查询,全部代码都贴上来,第一步可以选择不要

import requests

# 配置GitLab地址
GITLAB_URL = "http://10.10.10.10"
# 配置Token令牌
PRIVATE_TOKEN = "_dmeNrhyNymzbvSyNj6j"

# 这里用删除2024年1月1日以后创建的用户示范,还有多种查询参数,有特殊要求可以在查询结果中再筛选
def main():
    # Token令牌要放在header中,在这里统一配置,参数名必须大写
    headers = {"PRIVATE-TOKEN": PRIVATE_TOKEN}

    # 第一步:查询2024-01-01之后创建的所用用户,分页大小为100
    response_total = requests.get(
        f"{GITLAB_URL}/api/v4/users",
        # 查询时Token要放在header中
        headers = headers,
        params = {
            "created_after": "2024-01-01",
            "per_page": 100
        }
    )
    # 判断所用用户查询是否成功
    if response_total.status_code != 200:
        print(f"所有用户查询失败: {response_total.status_code}{response_total.text}")
        return
    # 获取需处理总用户数和总页数
    total_users = int(response_total.headers.get('X-Total', 1))
    total_pages = int(response_total.headers.get('X-Total-Pages', 1))
    print(f"总用户数: {total_users},总页数: {total_pages}")

    # 第二步:循环查询第一页
    page = 1
    while True:
        print(f"正在处理第 {page}/{total_pages} 页")
        # 在全部查询中加上分页号的参数
        response_users = requests.get(
            f"{GITLAB_URL}/api/v4/users",
            headers = headers,
            params = {
                "created_after": "2024-01-01",
                "per_page": 100,
            }
        )
        page += 1
        # 判断分页查询是否成功
        if response_users.status_code != 200:
            print(f"获取第页{page}用户失败: {response_users.status_code}{response_users.text}")
            continue
        elif response_users.text == "[]":
            print(f"获取第页{page}用户失败: 无数据,查询结束")
            break

        # 第三步:批量删除当前页用户,遍历当前分页的每一个用户,这里可以再额外筛选
        for user in response_users.json():
            response_delete = requests.delete(
                f"{GITLAB_URL}/api/v4/users/{user['id']}",
                headers = headers,
                # 删除用户时Token也可以放在参数里,但是参数名必须小写,在ApiPost中只能在参数小写,header中大小写都不行,奇怪
                # params = {"private_token": PRIVATE_TOKEN}
                params = {"hard_delete": "true"}
            )
            # 删除用户成功则返回204且无内容
            if response_delete.status_code != 204:
                print(f"删除失败 id:{user['id']} {user['username']},[{response_delete.status_code}]:{response_delete.text}")

if __name__ == "__main__":
    main()

控制台日志如下,因为有找不到删除失败的,所以处理了4次,最终ApiPost查确认是没有了,可能是访问太快导致GitLab删除失败
在这里插入图片描述
GitLab里也正常,groups还在恢复中
在这里插入图片描述
过了一会儿都恢复正常了,到这里处理就结束了
在这里插入图片描述
如果想用shell脚本来处理可以自己转化一下,或者直接复制给ai,但肯定是没有python方便调试和运行
希望大家在解决问题之外也能有所收获,都看到这里了,点个关注吧~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值