forked from kousu/xmpp_nextcloud_auth
-
Notifications
You must be signed in to change notification settings - Fork 1
/
nextcloud_auth
executable file
·78 lines (68 loc) · 3.7 KB
/
nextcloud_auth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/python3
"""
This is a prosody mod_auth_external plugin to auth against a nextcloud install
Tip: if you run into [ratelimiting](https://nextcloud.com/blog/security-in-nextcloud-12-bruteforce-protection-and-rate-limiting-for-developers/)
while testing this do
# psql nextcloud nextcloud
>> select * from oc_bruteforce_attempts;
>> delete from oc_bruteforce_attempts where ip = 'your_ip_address';
"""
import sys
import requests
import re
def nextcloud_auth(site, username, password):
"""
site: the base URL of a Nextcloud / Owncloud install
username: the username to try to login as
password: the password to login as
returns: whether the user could successfully log in, as a boolean
"""
# Nextcloud is heavily protected from all the web cruft with CSRF checking
# so we need to pretend to be enough of a web browser to play along
#
# TODO: We might be able to extract more efficiency by caching the session / CSRF token, and only refreshing it if challenged
# This whole thing could be an interactive coroutine (`next_request = yield result`) even. But that's for another day.
S = requests.Session()
with S.get("%s/login" % (site,), stream=True) as R:
# extract the CSRF token
# the token is usually near the top, so we only need read the first few lines
for l in R.iter_lines(decode_unicode=True): # XXX decode_unicode is a misnomer; it should be decode, or maybe text=True
r = re.search(r'<head data-requesttoken="(.*?)">', l)
if r:
requesttoken = r.groups()[0]
break
else:
raise RuntimeError("Could not find CSRF token in '%s/login'" % (site,))
requesttoken = None
# When oJSXC is installed, the CSRF token is submitted *as a custom header*
#R = S.post(site + "/apps/ojsxc/settings", headers={"requesttoken": requesttoken}, data={"username": username, "password": password})
# normalize the JSON result to a boolean; if we don't understand the result, return False
#return {"success": True, "noauth": False}.get(R.json().get('result', None), False)
with S.post(site + "/login", data={"user": username, "password": password, "requesttoken": requesttoken}, allow_redirects=False, stream=False) as R:
# -> if we go to /login again, we failed; if we go to /apps/files we succeeded. except, what if /apps/files isn't the app we're sent to? can you disable the files app?
if R.headers['Location'] == site + "/apps/files/": # XXXX this is dubious we should use urlparse.
return True
return False
def main(site):
for line in sys.stdin:
line = line.rstrip("\n") # strip trailing newline
try:
cmd, line = line.split(":",1) #XXX assumption: every command will have arguments, or if not, at least a : in it
if cmd == "auth":
username, domain, password = line.split(":",2)
print(int(nextcloud_auth(site, username, password)))
elif cmd == "isuser":
username, domain = line.split(":",1)
print("1") # XXX unimplemented: just pretend everyone is a valid user
elif cmd == "setpass":
#username, domain, password = line.split(":",2)
print("0") # XXX unimplemented: we can't change the password from here so don't bother
else:
# "Your script must respond with “0” for anything it doesn’t understand.
print("0")
except:
# "Your script must respond with “0” for anything it doesn’t understand.
print("0")
if __name__ == '__main__':
main(sys.argv[1])
#print(nextcloud_auth(*sys.argv[1:]))