Files
seafile-sso/seafile-ldap.py

275 lines
12 KiB
Python

#!/usr/bin/env python
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):
if data is None:
data = ''
else:
data = json.dumps(data)
url = '{0}/api/v2.1/{1}'.format(seafileURL, resource)
logger.debug('Request URL: {}'.format(url))
logger.debug('Request Data: {}'.format(data))
r = requests.request(
method,
url,
data=data,
headers={'Content-type': 'application/json','Accept': 'application/json; charset=utf-8; indent=4', 'Authorization': 'Token {0}'.format(seafileToken)},
)
logger.debug('Request Status Code: {}'.format(r.status_code))
if r.ok:
try:
logger.debug('Request Returned JSON: {}'.format(r.json()))
return {'ok': r.ok, 'status_code': r.status_code, 'response': r.json()}
except:
logger.debug('Request Returned Text: {}'.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']
logger.debug("Seafile Token: {0}".format(seafileToken))
logger.debug("Seafile ccnet.conf File Path: {0}".format(ccnetPath))
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.")
# 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 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))
# Connect to DB
dbconfig = {
'user': dbUser,
'password': dbPassword,
'host': dbHost,
'port': dbPort,
'database': dbName,
'charset': dbCharset,
'raise_on_warnings': True
}
cnx = mysql.connector.connect(**dbconfig)
cursor = cnx.cursor()
# Starting query for sql ldap users
logger.debug("Starting SQL query for LDAPUsers")
query = ("SELECT * FROM LDAPUsers")
logger.debug("Query: {0}".format(query))
cursor.execute(query)
sqlLDAPusers = cursor.fetchall()
logger.debug("Found {0} SQL LDAP users".format(cursor.rowcount))
for user in sqlLDAPusers:
logger.debug("User: {0} - Active: {1}".format(user[1], bool(user[4])))
logger.debug("Finished SQL query for LDAPUsers")
cnx.close()
# 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("Checking if LDAP user {0} is in SQl Table".format(ldapUser.mail))
cnx = mysql.connector.connect(**dbconfig)
cursor = cnx.cursor()
query = "SELECT * FROM LDAPUsers WHERE email = '{0}'".format(ldapUser.mail)
logger.debug("Query: {0}".format(query))
cursor.execute(query)
sqlLDAPuser = cursor.fetchall()
row_count = cursor.rowcount
logger.debug("Found {0} SQL LDAP user".format(row_count))
cnx.close()
if row_count == 1:
# User is in the sql table
# are they active
is_active = bool(sqlLDAPuser[0][4])
# log the results
logger.debug("LDAP User {0} is already in the SQL Table, 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 the SQL Table".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 SQL Table".format(ldapUser.mail))
else:
logger.error("Failed to add LDAP user {0} to the 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 sqlLDAPuser in sqlLDAPusers:
if sqlLDAPuser[4] == 0:
logger.debug("User {0} is already disabled in Seafile".format(sqlLDAPuser[1]))
continue
logger.debug("Searching for user {0} that has an email address, are enabled, and in the {1} group.".format(sqlLDAPuser[1], ldapFilter))
ldap.search(ldapBase, '(&(mail={0})(!(userAccountControl:1.2.840.113556.1.4.803:=2))({1}))'.format(sqlLDAPuser[1], 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(sqlLDAPuser[1]), seafileURL, seafileToken, "PUT", {"is_active": "false"})['response']
if not disableUserinSeafile['is_active']:
logger.info("User {0} was set to disabled in Seafile".format(sqlLDAPuser[1]))
else:
logger.error("There was an error setting user {0} to disabled in Seafile".format(sqlLDAPuser[1]))
# get ad groups and import them into seafile
# loop through each group and list members
# compare members to users in seafile group
# add users to group if missing and in the seafile group
# remove members in not in group or seafile group
# remove seafile groups if ad group is removed
ldap.search(ldapBase, '(objectClass=group)', attributes=['*'])
ldapGroups = []
for group in ldap.entries:
try:
if group.member:
logger.debug("Group {0} has members".format(group.name))
ldapGroups.append(group)
finally:
continue
seafileGroups = request('admin/groups/', seafileURL, seafileToken)['response']
for ldapGroup in ldapGroups:
searchSeafileGroup = request('admin/search-group/?query={0}'.format(ldapGroup.name), seafileURL, seafileToken)['response']['group_list']
# loop through the results and make sure we match on the group name
for seafileGroup in searchSeafileGroup:
if seafileGroup['name'] == ldapGroup.name:
seafileGroupMembers = request('admin/groups/{0}/members/'.format(seafileGroup.id), seafileURL, seafileToken)['response']
else:
continue
#logger.debug("Checking if LDAP group {0} is in SQL Table".format(ldapGroup.name))
#cnx = mysql.connector.connect(**dbconfig)
#cursor = cnx.cursor()
#query = "SELECT * FROM Group WHERE group_name = '{0}'".format(ldapGroup.name)
#logger.debug("Query: {0}".format(query))
#cursor.execute(query)
#sqlLDAPgroup = cursor.fetchall()
#row_count = cursor.rowcount
#logger.debug("Found {0} SQL LDAP Group".format(row_count))
#cnx.close()
#if row_count == 1:
# group is in seafile
#else:
# create the seafile group
# createSeafileGroup = request('admin/groups/', seafileURL, seafileToken, "POST", {"group_name": "{0}".format(ldapGroup.name), "group_owner": "admin@johnhgaunt.com"})['response']