Compare commits

...

18 commits

Author SHA1 Message Date
c44e457fd0 Merge pull request 'Corect code style for main Python module.' (#2) from bignose/ejabberd-auth-mastodon:wip/maintenance/code-style into master
Reviewed-on: chinwag/ejabberd-auth-mastodon#2
2023-01-01 10:50:40 +00:00
Ben Finney
2a7e4fcd6e Remove a statement using a name that is never defined in scope.
The intention may have been to close the “current” database cursor; but
there is no reference to that at this point in the code.
2022-12-19 09:52:20 +11:00
Ben Finney
1ad357ba4a Specify the ‘Exception’ catch-all exception class, not a bare ‘except’.
PEP 8 specifies:

* When catching exceptions, mention specific exceptions whenever possible
  instead of using a bare `except:` clause […]

  A bare `except:` clause will catch `SystemExit` and `KeyboardInterrupt`
  exceptions, making it harder to interrupt a program with Control-C, and
  can disguise other problems. If you want to catch all exceptions that
  signal program errors, use `except Exception:` (bare except is equivalent
  to `except BaseException:`).
2022-12-19 09:50:30 +11:00
Ben Finney
6a4de62467 Remove an assignment to an unused name. 2022-12-19 09:50:30 +11:00
Ben Finney
7118641d78 Use identity operators for comparison to None singleton.
PEP 8 specifies:

* Comparisons to singletons like None should always be done with `is` or
  `is not`, never the equality operators.
2022-12-19 09:49:01 +11:00
Ben Finney
4bed400c7d Use equality operator for comparison to integer value. 2022-12-19 09:49:01 +11:00
Ben Finney
a88f298e98 Wrap long block comment lines at 72 columns. 2022-12-19 09:26:13 +11:00
Ben Finney
9325300e39 Wrap long statements across multiple lines.
PEP 8 specifies:

* Limit all lines to a maximum of 79 characters.

* The preferred way of wrapping long lines is by using Python’s implied
  line continuation inside parentheses, brackets and braces. Long lines can
  be broken over multiple lines by wrapping expressions in parentheses.
  These should be used in preference to using a backslash for line
  continuation.
2022-12-19 09:26:03 +11:00
Ben Finney
34ab6489ba Reformat SQL statement for clarity and wrap the long line. 2022-12-19 09:25:58 +11:00
Ben Finney
237abe539a Use whitespace around operators in conformance with PEP 8.
PEP 8 specifies:

* A single space around binary operators (e.g. ‘=’ assignment, ‘%’
  modulus).

* No surrounding space around ‘=’ in keyword argument assignment for a
  function call.
2022-12-19 09:25:54 +11:00
Ben Finney
9a2d05e51f Use ‘# ’ comment leader for block comments.
PEP 8 specifies:

* Each line of a block comment starts with a # and a single space (unless
  it is indented text inside the comment).
2022-12-19 09:25:45 +11:00
Ben Finney
512114517c Remove unused imports. 2022-12-19 09:25:35 +11:00
Ben Finney
3ef1d5daae Use a separate ‘import’ statement for each module/package, grouped.
PEP 8 specifies:

* Imports should usually be on separate lines.

* Imports are always put at the top of the file, just after any module
  comments and docstrings, and before module globals and constants.

* Imports should be grouped in the following order:

  * Standard library imports.
  * Related third party imports.
  * Local application/library specific imports.
2022-12-19 09:25:24 +11:00
Ben Finney
e30ab77cf4 Use structural blank lines conformant with PEP 8.
PEP 8 specifies:

* Surround top-level function and class definitions with two blank lines.

* Method definitions inside a class are surrounded by a single blank line.
2022-12-19 09:25:10 +11:00
Ben Finney
dc09a6be92 Normalise indentation levels by wrapping at open parenthesis.
PEP 8 specifies:

* Use 4 spaces per indentation level.

* Continuation lines should align wrapped elements either vertically using
  Python’s implicit line joining inside parentheses, brackets and braces,
  or using a hanging indent.
2022-12-19 09:24:54 +11:00
Ben Finney
c6c19a1bcf Convert indentation to use U+0020 SPACE characters.
PEP 8 specifies:

* Use 4 spaces per indentation level.

* Spaces (U+0008) are the preferred indentation method.
2022-12-19 09:24:48 +11:00
Ben Finney
ebd066b170 Remove trailing whitespace. 2022-12-19 09:24:40 +11:00
Ben Finney
de4e2027a1 Specify EditorConfig settings for this code base. 2022-12-19 09:24:31 +11:00
3 changed files with 177 additions and 106 deletions

34
.editorconfig Normal file
View file

@ -0,0 +1,34 @@
# .editorconfig
# EditorConfig settings for this code base.
# See documentation at <URL:http://editorconfig.org/>.
# Is this the top-most EditorConfig config file in the code base?
root = true
# Match all file names, unless more specific match later.
[*]
# Text encoding name.
charset = utf-8
# Remove trailing whitespace on lines?
trim_trailing_whitespace = true
# End-of-line style (“lf”, “cr”, “crlf”).
end_of_line = lf
# Ensure file ends with a line break?
insert_final_newline = true
# Character to use for indentation (“tab“ for U+0009, “space“ for U+0020).
indent_style = space
# Number of columns for each indentation level.
indent_size = 4
# Local variables:
# coding: utf-8
# mode: conf
# End:
# vim: fileencoding=utf-8 filetype=dosini :

View file

@ -3,7 +3,7 @@ This is a Python script designed to run as an [ejabberd external auth](https://d
The code is derived from [ejabberd-auth-mysql](https://github.com/rankenstein/ejabberd-auth-mysql) and is licensed under the GNU GPLv3.
It is used on [Chinwag Social](https://social.chinwag.org) to provide XMPP messaging functionality to every account.
It is used on [Chinwag Social](https://social.chinwag.org) to provide XMPP messaging functionality to every account.
Discussion, questions and/or just saying hi in the [Chinwag Social Beergarden](xmpp:beergarden@rooms.chinwag.org?join) MUC (beergarden@rooms.chinwag.org) is welcomed!

View file

@ -1,165 +1,202 @@
#!/usr/bin/env python3
import os, sys, logging, struct, psycopg2, bcrypt, random, atexit, time
# Database connection details. The credentials here need access to the Mastodon database, which "ejabberd" is unlikely
# to have on your system by default. You shoud grant SELECT privileges to ejabberd on the "accounts" and "users" tables,
# to play it safe, or include the Mastodon DB user credentials here (don't).
db_host="localhost"
db_port=5432
db_user="ejabberd"
db_pass=""
db_name="mastodon"
import atexit
import logging
import struct
import sys
import time
# This is the query that pulls the password hash for the given user. Mastodon doesn't store the domain for local accounts in
# the database, so we ignore the host component and try to match username where the domain is NULL.
db_query_getpass="select users.encrypted_password as password from accounts inner join users on accounts.id=users.account_id where lower(accounts.username) = %(user)s and accounts.domain is null"
import bcrypt
import psycopg2
# Database connection details. The credentials here need access to the
# Mastodon database, which "ejabberd" is unlikely to have on your system
# by default. You shoud grant SELECT privileges to ejabberd on the
# "accounts" and "users" tables, to play it safe, or include the
# Mastodon DB user credentials here (don't).
db_host = "localhost"
db_port = 5432
db_user = "ejabberd"
db_pass = ""
db_name = "mastodon"
# This is the query that pulls the password hash for the given user.
# Mastodon doesn't store the domain for local accounts in the database,
# so we ignore the host component and try to match username where the
# domain is NULL.
db_query_getpass = """
SELECT users.encrypted_password AS password
FROM accounts
INNER JOIN users
ON accounts.id = users.account_id
WHERE
lower(accounts.username) = %(user)s
AND accounts.domain IS NULL
"""
########################################################################
#Setup
# Setup
########################################################################
sys.stderr = open('/var/log/ejabberd/extauth_err.log', 'a')
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
filename='/var/log/ejabberd/extauth.log',
filemode='a')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(levelname)s %(message)s',
filename='/var/log/ejabberd/extauth.log',
filemode='a',
)
try:
# Connect to the DB, set autocommit and readonly otherwise postgresql seems to have a
# tendency to keep things "idle in transaction" and table locks eventually grind
# Mastodon to a halt. We don't make any changes anyway.
database=psycopg2.connect(host = db_host, user = db_user, password = db_pass, database = db_name, port = db_port)
database.set_session(readonly=True, autocommit=True)
logging.debug(database.get_dsn_parameters())
except:
logging.error("Unable to initialize database, check settings!")
time.sleep(10)
sys.exit(1)
# Connect to the DB, set autocommit and readonly otherwise
# postgresql seems to have a tendency to keep things "idle in
# transaction" and table locks eventually grind Mastodon to a halt.
# We don't make any changes anyway.
database = psycopg2.connect(
host=db_host,
user=db_user,
password=db_pass,
database=db_name,
port=db_port,
)
database.set_session(readonly=True, autocommit=True)
logging.debug(database.get_dsn_parameters())
except Exception:
logging.error("Unable to initialize database, check settings!")
time.sleep(10)
sys.exit(1)
@atexit.register
def close_db():
cursor.close()
database.close()
database.close()
logging.info('auth-mastodon script started, waiting for ejabberd requests')
class EjabberdInputError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
########################################################################
#Declarations
# Declarations
########################################################################
def ejabberd_in():
logging.debug("trying to read 2 bytes from ejabberd:")
logging.debug("trying to read 2 bytes from ejabberd:")
input_length = sys.stdin.buffer.read(2)
input_length = sys.stdin.buffer.read(2)
if len(input_length) is not 2:
logging.debug("ejabberd sent us wrong things!")
raise EjabberdInputError('Wrong input from ejabberd!')
if len(input_length) != 2:
logging.debug("ejabberd sent us wrong things!")
raise EjabberdInputError('Wrong input from ejabberd!')
logging.debug('got 2 bytes via stdin: %s'%input_length)
logging.debug('got 2 bytes via stdin: %s' % input_length)
(size,) = struct.unpack('>h', input_length)
logging.debug('size of data: %i'%size)
(size,) = struct.unpack('>h', input_length)
logging.debug('size of data: %i' % size)
income=sys.stdin.read(size)
logging.debug("incoming data: %s"%income)
income = sys.stdin.read(size)
logging.debug("incoming data: %s" % income)
return income
return income
def ejabberd_out(bool):
logging.debug("Ejabberd gets: %s" % bool)
logging.debug("Ejabberd gets: %s" % bool)
token = genanswer(bool)
token = genanswer(bool)
logging.debug("sent bytes: %#x %#x %#x %#x" % (token[0], token[1], token[2], token[3]))
logging.debug(
"sent bytes: %#x %#x %#x %#x"
% (token[0], token[1], token[2], token[3]))
sys.stdout.buffer.write(token)
sys.stdout.buffer.flush()
sys.stdout.buffer.write(token)
sys.stdout.buffer.flush()
def genanswer(bool):
answer = 0
if bool:
answer = 1
token = struct.pack('>hh', 2, answer)
return token
answer = 0
if bool:
answer = 1
token = struct.pack('>hh', 2, answer)
return token
def get_password(user, host):
# Right now we ignore the host component, as Mastodon doesn't store it for local accounts.
# It may be required one day, so the code to handle passing it to the query is left in for now.
cursor = database.cursor()
cursor.execute(db_query_getpass, {"user": user.lower(), "host": host})
data = cursor.fetchone()
cursor.close()
return data[0] if data != None else None
# Right now we ignore the host component, as Mastodon doesn't store
# it for local accounts. It may be required one day, so the code to
# handle passing it to the query is left in for now.
cursor = database.cursor()
cursor.execute(db_query_getpass, {"user": user.lower(), "host": host})
data = cursor.fetchone()
cursor.close()
return data[0] if data is not None else None
def isuser(user, host):
return get_password(user, host) != None
return get_password(user, host) is not None
def auth(user, host, password):
db_password = get_password(user, host)
if db_password == None:
logging.debug("Wrong username: %s@%s" % (user, host))
return False
else:
if bcrypt.checkpw(password.encode('utf8'), db_password.encode('utf8')):
logging.debug("Validated %s against hash %s" % (user, db_password))
return True
else:
logging.debug("Wrong password for user: %s@%s" % (user, host))
return False
db_password = get_password(user, host)
if db_password is None:
logging.debug("Wrong username: %s@%s" % (user, host))
return False
else:
if bcrypt.checkpw(password.encode('utf8'), db_password.encode('utf8')):
logging.debug("Validated %s against hash %s" % (user, db_password))
return True
else:
logging.debug("Wrong password for user: %s@%s" % (user, host))
return False
########################################################################
#Main Loop
# Main Loop
########################################################################
exitcode=0
exitcode = 0
while True:
logging.debug("start of infinite loop")
logging.debug("start of infinite loop")
try:
ejab_request = ejabberd_in().split(':', 3)
except EOFError:
break
except Exception as e:
logging.exception("Exception occured while reading stdin")
raise
try:
ejab_request = ejabberd_in().split(':', 3)
except EOFError:
break
except Exception:
logging.exception("Exception occured while reading stdin")
raise
op_result = False
try:
# Only 'auth' and 'isuser' implemented, placeholders left to maybe
# expose other functions later but for now let's not even think about
# modifying the Mastodon DB
if ejab_request[0] == "auth":
op_result = auth(ejab_request[1], ejab_request[2], ejab_request[3])
elif ejab_request[0] == "isuser":
op_result = isuser(ejab_request[1], ejab_request[2])
elif ejab_request[0] == "setpass":
op_result = False
elif ejab_request[0] == "tryregister":
op_result = False
elif ejab_request[0] == "removeuser":
op_result = False
elif ejab_request[0] == "removeuser3":
op_result = False
except Exception:
logging.exception("Exception occured")
op_result = False
try:
# Only 'auth' and 'isuser' implemented, placeholders left to
# maybe expose other functions later but for now let's not even
# think about modifying the Mastodon DB
if ejab_request[0] == "auth":
op_result = auth(ejab_request[1], ejab_request[2], ejab_request[3])
elif ejab_request[0] == "isuser":
op_result = isuser(ejab_request[1], ejab_request[2])
elif ejab_request[0] == "setpass":
op_result = False
elif ejab_request[0] == "tryregister":
op_result = False
elif ejab_request[0] == "removeuser":
op_result = False
elif ejab_request[0] == "removeuser3":
op_result = False
except Exception:
logging.exception("Exception occured")
ejabberd_out(op_result)
logging.info("successful" if op_result else "unsuccessful")
ejabberd_out(op_result)
logging.info("successful" if op_result else "unsuccessful")
logging.debug("end of infinite loop")
logging.info('extauth script terminating')