From 93f8881007a96e8daa6df1e2e9620503dc8b4d24 Mon Sep 17 00:00:00 2001 From: John Gaunt Date: Mon, 23 May 2022 18:15:30 -0400 Subject: [PATCH] working towards SSO user checking, forget about groups --- seafile-sso.py | 241 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 seafile-sso.py diff --git a/seafile-sso.py b/seafile-sso.py new file mode 100644 index 0000000..f4a760c --- /dev/null +++ b/seafile-sso.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python + +from ldap import filter +from ldap3 import Connection, Server, ANONYMOUS, SIMPLE, SYNC, ASYNC, core +from getpass import getpass +import configparser +import logging +import argparse +import os +import requests +import urllib3 +import json +import mysql.connector + +def request(resource, seafileURL, seafileToken, method='GET', data=None, dataIsJson=True): + if data is None: + httpHeaders = {'Accept': 'application/json; charset=utf-8; indent=4', 'Authorization': 'Token {0}'.format(seafileToken)} + data = '' + else: + if dataIsJson: + data = json.dumps(data) + httpHeaders = {'Content-type': 'application/json', 'Accept': 'application/json; charset=utf-8; indent=4', 'Authorization': 'Token {0}'.format(seafileToken)} + else: + httpHeaders = {'Content-type': 'application/x-www-form-urlencoded', 'Accept': 'application/json; charset=utf-8; indent=4', 'Authorization': 'Token {0}'.format(seafileToken)} + url = '{0}/api/v2.1/{1}'.format(seafileURL, resource) + logger.debug('Request URL: {0}'.format(url)) + logger.debug('Request Method: {0}'.format(method)) + logger.debug('Request Headers: {0}'.format(httpHeaders)) + logger.debug('Request Data: {0}'.format(data)) + r = requests.request( + method, + url, + data = data, + headers = httpHeaders, + ) + logger.debug('Response: {0}'.format(r)) + logger.debug('Request Status Code: {0}'.format(r.status_code)) + if r.ok: + try: + logger.debug('Request Returned JSON: {0}'.format(r.json())) + return {'ok': r.ok, 'status_code': r.status_code, 'response': r.json()} + except: + logger.debug('Request Returned Text: {0}'.format(r.text)) + return {'ok': r.ok, 'status_code': r.status_code, 'response': r.text} + raise ValueError(r) + +#def +parser = argparse.ArgumentParser(description='Sync LDAP with Seafile') + #group = parser.add_mutually_exclusive_group() + #group.add_argument('-e', '--encrypt', action='store_true', help='encrypt') + #group.add_argument('-d', '--decrypt', action='store_true', help='decrypt') + #parser.add_argument('-t', '--text', type=str, help='text to encrypt/decrypt') + #group2 = parser.add_mutually_exclusive_group() + #group2.add_argument('-t', '--text', type=str, help='text to encrypt/decrypt') + #group2.add_argument('-f', '--file', type=str, help='file to encrypt/decrypt, will create a .pynacl file when encrypting, and requires .pynacl file to decrypt') +parser.add_argument('-v', '--verbose', action='store_true', help='increase log level to debug') +args = parser.parse_args() + + +logger = logging.getLogger(__name__) +logFormatter = logging.Formatter('%(asctime)s - [%(levelname)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S') +logger.setLevel(logging.INFO) +if args.verbose: + logger.setLevel(logging.DEBUG) +else: + logger.setLevel(logging.INFO) + +#fileHandler = logging.FileHandler("/tmp/seafile-ldap.log") +#fileHandler.setFormatter(logFormatter) +#logger.addHandler(fileHandler) + +consoleHandler = logging.StreamHandler() +consoleHandler.setFormatter(logFormatter) +logger.addHandler(consoleHandler) + +# get directory of script +cwd = os.path.dirname(os.path.realpath(__file__)) +configPath = os.path.join(cwd, 'config.ini') + +# import the config file +logger.debug("Starting to read the config.ini file.") +logger.debug("Using file {0}.".format(configPath)) +if os.path.exists(configPath): + config = configparser.ConfigParser() + config.read(configPath) +else: + logger.critical("Unable to find/read config file. Please ensure the config.ini is in the same directory as this script and readable.") + exit(1) + +seafileToken = config['Seafile']['token'] +ccnetPath = config['Seafile']['ccnetPath'] +adminEmail = config['Seafile']['adminEmail'] +logger.debug("Seafile Token: {0}".format(seafileToken)) +logger.debug("Seafile ccnet.conf File Path: {0}".format(ccnetPath)) +logger.debug("Seafile Admin Email: {0}".format(adminEmail)) +logger.debug("Finished reading the config.ini file.") +logger.debug("Starting to read the ccnet.conf file.") +if os.path.exists(ccnetPath): + ccnetConfig = configparser.ConfigParser() + ccnetConfig.read(ccnetPath) +else: + logger.critical("Unable to find/read ccnet.conf file. Please ensure the ccnet.conf path is correct in the config.ini.") + exit(2) + +# Seafile url +seafileURL = ccnetConfig['General']['SERVICE_URL'] + +# DB config +dbEngine = ccnetConfig['Database']['ENGINE'] +dbHost = ccnetConfig['Database']['HOST'] +dbPort = ccnetConfig['Database'].getint('PORT') +dbUser = ccnetConfig['Database']['USER'] +dbPassword = ccnetConfig['Database']['PASSWD'] +dbName = ccnetConfig['Database']['DB'] +dbCharset = ccnetConfig['Database']['CONNECTION_CHARSET'] +logger.debug("DB Engine: {0}, DB Host: {1}, DB Port: {2}, DB User: {3}, DB Name: {4}, DB Connection Charset: {5}".format(dbEngine, dbHost, dbPort, dbUser, dbName, dbCharset)) + +# ldap Config +ldapHost = ccnetConfig['LDAP']['HOST'] +#ldapPort = ccnetConfig['LDAP SERVER'].getint('port') +#ldapSSL = ccnetConfig['LDAP SERVER'].getboolean('ssl') +ldapBase = ccnetConfig['LDAP']['BASE'] +ldapUserDN = ccnetConfig['LDAP']['USER_DN'] +ldapUserPassword = ccnetConfig['LDAP']['PASSWORD'] +ldapFilter = ccnetConfig['LDAP']['FILTER'] +logger.debug("LDAP Host: {0}, LDAP Base: {1}, LDAP User DN: {2}, LDAP Filter: {3}".format(ldapHost, ldapBase, ldapUserDN, ldapFilter)) + +logger.debug("Finished reading the ccnet.conf file.") + +# Config DB Varaibles +dbconfig = { + 'user': dbUser, + 'password': dbPassword, + 'host': dbHost, + 'port': dbPort, + 'database': dbName, + 'charset': dbCharset, + 'raise_on_warnings': True +} + +# setup the server +ldapServer = Server(ldapHost) +logger.debug("Setup LDAP server connection uri: {0}".format(ldapServer)) +try: + ldap = Connection(ldapServer, ldapUserDN, ldapUserPassword, auto_bind=True) +except core.exceptions.LDAPBindError as e: + logger.critical("LDAP Bind Failed. {0}".format(e)) + exit() +logger.debug("Bind successful.") + + +# get seafile users and loop through and check group membership and disable or not + + +# Get seafile users from LDAP +logger.debug("Searching for users that have a email address, are enabled, and in the {} group.".format(ldapFilter)) +ldap.search(ldapBase, '(&(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2))({0}))'.format(ldapFilter), attributes=['*']) +logger.debug("Found {0} LDAP users.".format(len(ldap.entries))) +ldapUsers = ldap.entries +for user in ldapUsers: + logger.debug("User: {0} - Email: {1} - UserDN: {2}".format(user.displayName, user.mail, user.distinguishedName)) + + +# Starting query for seafile ldap users +seafileLDAPUsers = [] +logger.debug("Starting query for LDAPUsers in Seafile") +seafileUsers = request('admin/search-user/?query=@{0}'.format(adminEmail.split("@")[1]), seafileURL, seafileToken)['response']['user_list'] +# need to substract one from the len as the admin account is in the list +logger.debug("Found {0} Seafile LDAP users".format(len(seafileUsers)-1)) +for seafileUser in seafileUsers: + if seafileUser['email'] == adminEmail: + continue + else: + logger.debug("User: {0} - Active: {1}".format(seafileUser['email'], bool(seafileUser['is_active']))) + seafileLDAPUsers.append(seafileUser) + + + +# Loop through the ldap users and make sure they are in the sql ldap users table +# if they are not in the sql table, insert a new row to add them +# if they are disabled in the sql table, enable them +for ldapUser in ldapUsers: + logger.debug("Searching if LDAP user {0} is in Seafile".format(ldapUser.mail)) + checkSeafileUser = request('admin/search-user/?query={0}'.format(ldapUser.mail), seafileURL, seafileToken)['response']['user_list'] + # loop through the results and make sure we match on the email + for seafileUser in checkSeafileUser: + if seafileUser['email'] == ldapUser.mail: + # User is in the sql table + # are they active + is_active = bool(seafileUser['is_active']) + # log the results + logger.debug("LDAP User {0} is already in Seafile, Is Active: {1}".format(ldapUser.mail, is_active)) + # if user is not active, they should be + if not is_active: + logger.info("User {0} is NOT active in Seafile".format(ldapUser.mail)) + # call the api to enable the user in seafile + enableSeafileUser = request('admin/users/{0}/'.format(ldapUser.mail), seafileURL, seafileToken, "PUT", {"is_active": "true"})['response'] + if enableSeafileUser['is_active']: + logger.info("User {0} was set to active in Seafile".format(ldapUser.mail)) + else: + logger.error("There was an error setting user {0} to active in Seafile".format(ldapUser.mail)) + # user is not in the SQL table + else: + logger.info("LDAP User {0} is NOT in Seafile".format(ldapUser.mail)) + # add user to ldap table + cnx = mysql.connector.connect(**dbconfig) + cursor = cnx.cursor() + query = "INSERT INTO LDAPUsers (email, password, is_staff, is_active) VALUES ('{0}', '', {1}, {2})".format(ldapUser.mail, 0, 1) + logger.debug("Query: {0}".format(query)) + cursor.execute(query) + cnx.commit() + row_count = cursor.rowcount + if row_count == 1: + logger.info("LDAP user {0} was added to the Seafile SQL Table".format(ldapUser.mail)) + else: + logger.error("Failed to add LDAP user {0} to the Seafile SQL Table".format(ldapUser.mail)) + cnx.close() + # Update seafile user profile with new name + updateSeafileUserName = request('admin/users/{0}/'.format(ldapUser.mail), seafileURL, seafileToken, "PUT", {"name": "{0}".format(ldapUser.displayName)}) + if updateSeafileUserName['ok']: + logger.debug("User {0} name was updated to {1}".format(ldapUser.mail, ldapUser.displayName)) + else: + logger.error("There was an error setting user {0} name to {1}".format(ldapUser.mail, ldapUser.displayName)) + +# Loop through the sql ldap users and disable those not in the ldap list +for seafileLDAPUser in seafileLDAPUsers: + if not seafileLDAPUser['is_active']: + logger.debug("User {0} is already disabled in Seafile".format(seafileLDAPUser['email'])) + continue + logger.debug("Searching for user {0} that has an email address, are enabled, and in the {1} group.".format(seafileLDAPUser['email'], ldapFilter)) + ldap.search(ldapBase, '(&(mail={0})(!(userAccountControl:1.2.840.113556.1.4.803:=2))({1}))'.format(seafileLDAPUser['email'], ldapFilter), attributes=['*']) + count = len(ldap.entries) + logger.debug("Found {0} LDAP user.".format(count)) + if count == 0: + # User is not enabled, have email, or in the group, disable their account + disableUserinSeafile = request('admin/users/{0}/'.format(seafileLDAPUser['email']), seafileURL, seafileToken, "PUT", {"is_active": "false"})['response'] + if not disableUserinSeafile['is_active']: + logger.info("User {0} was set to disabled in Seafile".format(seafileLDAPUser['email'])) + else: + logger.error("There was an error setting user {0} to disabled in Seafile".format(seafileLDAPUser['email'])) +