Reference Synapse Identity Verification and Lookup Server
This commit is contained in:
commit
2360cd427f
177
LICENSE
Normal file
177
LICENSE
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
32
README.rst
Normal file
32
README.rst
Normal file
@ -0,0 +1,32 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
Dependencies can be installed using setup.py in the same way as synapse: see synpase/README.rst.
|
||||
|
||||
Having installed dependencies, you can run sydent using::
|
||||
|
||||
$ python -m sydent.sydent
|
||||
|
||||
This will create a configuration file in sydent.conf with some defaults. You'll most likley want to change the server name and specify a mail relay.
|
||||
|
||||
Requests
|
||||
========
|
||||
|
||||
The requests that synapse servers and clients submit to the identity server are, briefly, as follows:
|
||||
|
||||
curl -XPOST 'http://localhost:8001/matrix/identity/api/v1/validate/email/requestToken' -d'email=matthew@arasphere.net&clientSecret=abcd'
|
||||
{"success": true, "tokenId": 1}
|
||||
|
||||
# receive 943258 by mail
|
||||
|
||||
curl -XPOST 'http://localhost:8001/matrix/identity/api/v1/validate/email/submitToken' -d'token=943258&tokenId=1'
|
||||
{"success": true}
|
||||
|
||||
# lookup:
|
||||
|
||||
curl 'http://localhost:8001/matrix/identity/api/v1/lookup?medium=email&address=henry%40matrix.org'
|
||||
|
||||
# fetch pubkey key for a server
|
||||
|
||||
curl http://localhost:8001/matrix/identity/api/v1/pubkey/ed25519
|
||||
|
5
res/email.template
Normal file
5
res/email.template
Normal file
@ -0,0 +1,5 @@
|
||||
Hi!
|
||||
|
||||
Your email validation token is {token}.
|
||||
|
||||
If you were not expecting this message, it is safe to ignore it.
|
10
setup.cfg
Normal file
10
setup.cfg
Normal file
@ -0,0 +1,10 @@
|
||||
[build_sphinx]
|
||||
source-dir = docs/sphinx
|
||||
build-dir = docs/build
|
||||
all_files = 1
|
||||
|
||||
[aliases]
|
||||
test = trial
|
||||
|
||||
[trial]
|
||||
test_suite = tests
|
51
setup.py
Normal file
51
setup.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
# Utility function to read the README file.
|
||||
# Used for the long_description. It's nice, because now 1) we have a top level
|
||||
# README file and 2) it's easier to type in the README file than to put a raw
|
||||
# string in below ...
|
||||
def read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
setup(
|
||||
name="SynapseIdentityServer",
|
||||
version="0.1",
|
||||
packages=find_packages(exclude=["tests"]),
|
||||
description="Reference Synapse Identity Verification and Lookup Server",
|
||||
install_requires=[
|
||||
"syutil==0.0.1",
|
||||
"Twisted>=14.0.0",
|
||||
"service_identity>=1.0.0",
|
||||
"pyasn1",
|
||||
"pynacl",
|
||||
"daemonize",
|
||||
],
|
||||
dependency_links=[
|
||||
"git+ssh://git@git.openmarket.com/tng/syutil.git#egg=syutil-0.0.1",
|
||||
],
|
||||
setup_requires=[
|
||||
"setuptools_trial",
|
||||
"setuptools>=1.0.0", # Needs setuptools that supports git+ssh. It's not obvious when support for this was introduced.
|
||||
"mock"
|
||||
],
|
||||
include_package_data=True,
|
||||
long_description=read("README"),
|
||||
)
|
0
sydent/__init__.py
Normal file
0
sydent/__init__.py
Normal file
0
sydent/db/__init__.py
Normal file
0
sydent/db/__init__.py
Normal file
45
sydent/db/sqlitedb.py
Normal file
45
sydent/db/sqlitedb.py
Normal file
@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sqlite3
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SqliteDatabase:
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
dbFilePath = self.sydent.cfg.get("db", "db.file")
|
||||
logger.info("Using DB file %s", dbFilePath)
|
||||
|
||||
self.db = sqlite3.connect(dbFilePath)
|
||||
|
||||
schemaDir = os.path.dirname(__file__)
|
||||
|
||||
c = self.db.cursor()
|
||||
|
||||
for f in os.listdir(schemaDir):
|
||||
if not f.endswith(".sql"):
|
||||
continue
|
||||
scriptPath = os.path.join(schemaDir, f)
|
||||
fp = open(scriptPath, 'r')
|
||||
c.executescript(fp.read())
|
||||
fp.close()
|
||||
|
||||
c.close()
|
||||
self.db.commit()
|
18
sydent/db/threepid_associations.sql
Normal file
18
sydent/db/threepid_associations.sql
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 matrix.org
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
CREATE TABLE if not exists threepid_associations (id integer primary key, medium varchar(16) not null, address varchar(256) not null, mxId varchar(256) not null, createdAt bigint not null, expires bigint not null);
|
||||
CREATE UNIQUE INDEX if not exists medium_address on threepid_associations(medium, address);
|
18
sydent/db/threepid_validation.sql
Normal file
18
sydent/db/threepid_validation.sql
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2014 matrix.org
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS threepid_validation_tokens (id integer primary key, medium varchar(16) not null, address varchar(256) not null, token varchar(32) not null, clientSecret varchar(32) not null, validated int default 0, createdAt bigint not null);
|
||||
|
41
sydent/db/threepidtoken.py
Normal file
41
sydent/db/threepidtoken.py
Normal file
@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from sydent.validators import Token
|
||||
|
||||
class ThreePidTokenStore:
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
def addToken(self, medium, address, token, clientSecret, createdAt):
|
||||
cur = self.sydent.db.cursor()
|
||||
|
||||
cur.execute("insert into threepid_validation_tokens ('medium', 'address', 'token', 'clientSecret', 'createdAt')"+
|
||||
" values (?, ?, ?, ?, ?)", (medium, address, token, clientSecret, createdAt))
|
||||
self.sydent.db.commit()
|
||||
return cur.lastrowid
|
||||
|
||||
def getTokenById(self, tokId):
|
||||
cur = self.sydent.db.cursor()
|
||||
|
||||
cur.execute("select id, medium, address, token, clientSecret, validated, createdAt from "+
|
||||
"threepid_validation_tokens where id = ?", [tokId])
|
||||
row = cur.fetchone()
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
return Token(row[0], row[1], row[2], row[3], row[4], row[5], row[6])
|
0
sydent/http/__init__.py
Normal file
0
sydent/http/__init__.py
Normal file
71
sydent/http/httpserver.py
Normal file
71
sydent/http/httpserver.py
Normal file
@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.web.server import Site
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.python import log
|
||||
|
||||
import logging
|
||||
import twisted.internet.reactor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class HttpServer:
|
||||
def __init__(self, sydent):
|
||||
self.sydent = sydent
|
||||
|
||||
observer = log.PythonLoggingObserver()
|
||||
observer.start()
|
||||
|
||||
root = Resource()
|
||||
matrix = Resource()
|
||||
identity = Resource()
|
||||
api = Resource()
|
||||
v1 = Resource()
|
||||
|
||||
validate = Resource()
|
||||
email = Resource()
|
||||
emailReqCode = self.sydent.servlets.emailRequestCode
|
||||
emailValCode = self.sydent.servlets.emailValidate
|
||||
|
||||
lookup = self.sydent.servlets.lookup
|
||||
|
||||
pubkey = Resource()
|
||||
pk_ed25519 = self.sydent.servlets.pubkey_ed25519
|
||||
|
||||
root.putChild('matrix', matrix)
|
||||
matrix.putChild('identity', identity)
|
||||
identity.putChild('api', api)
|
||||
api.putChild('v1', v1)
|
||||
|
||||
v1.putChild('validate', validate)
|
||||
validate.putChild('email', email)
|
||||
|
||||
v1.putChild('lookup', lookup)
|
||||
|
||||
v1.putChild('pubkey', pubkey)
|
||||
pubkey.putChild('ed25519', pk_ed25519)
|
||||
|
||||
email.putChild('requestToken', emailReqCode)
|
||||
email.putChild('submitToken', emailValCode)
|
||||
|
||||
self.factory = Site(root)
|
||||
|
||||
def run(self):
|
||||
httpPort = int(self.sydent.cfg.get('http', 'http.port'))
|
||||
logger.info("Starting HTTP server on port %d", httpPort)
|
||||
twisted.internet.reactor.listenTCP(httpPort, self.factory)
|
||||
twisted.internet.reactor.run()
|
0
sydent/http/servlets/__init__.py
Normal file
0
sydent/http/servlets/__init__.py
Normal file
97
sydent/http/servlets/emailservlet.py
Normal file
97
sydent/http/servlets/emailservlet.py
Normal file
@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
from sydent.validators.emailvalidator import EmailAddressException, EmailSendException
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def send_cors(request):
|
||||
request.setHeader(b"Content-Type", b"application/json")
|
||||
request.setHeader("Access-Control-Allow-Origin", "*")
|
||||
request.setHeader("Access-Control-Allow-Methods",
|
||||
"GET, POST, PUT, DELETE, OPTIONS")
|
||||
request.setHeader("Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept")
|
||||
|
||||
|
||||
class EmailRequestCodeServlet(Resource):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
def render_POST(self, request):
|
||||
send_cors(request)
|
||||
if 'email' not in request.args or 'clientSecret' not in request.args:
|
||||
request.setResponseCode(400)
|
||||
resp = {'error': 'badrequest', 'message': "'email' and 'clientSecret' fields are required"}
|
||||
return json.dumps(resp)
|
||||
|
||||
email = request.args['email'][0]
|
||||
clientSecret = request.args['clientSecret'][0]
|
||||
|
||||
resp = None
|
||||
|
||||
try:
|
||||
tokenId = self.sydent.validators.email.requestToken(email, clientSecret)
|
||||
except EmailAddressException:
|
||||
request.setResponseCode(400)
|
||||
resp = {'error': 'email_invalid'}
|
||||
except EmailSendException:
|
||||
request.setResponseCode(500)
|
||||
resp = {'error': 'send_error'}
|
||||
|
||||
if not resp:
|
||||
resp = {'success':True, 'tokenId':tokenId}
|
||||
|
||||
return json.dumps(resp).encode("UTF-8")
|
||||
|
||||
def render_OPTIONS(self, request):
|
||||
send_cors(request)
|
||||
request.setResponseCode(200)
|
||||
return "{}".encode("UTF-8")
|
||||
|
||||
class EmailValidateCodeServlet(Resource):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
def render_POST(self, request):
|
||||
send_cors(request)
|
||||
if 'tokenId' not in request.args or 'token' not in request.args or 'mxId' not in request.args:
|
||||
request.setResponseCode(400)
|
||||
resp = {'error': 'badrequest', 'message': "'tokenId', 'token' and 'mxId' fields are required"}
|
||||
return json.dumps(resp)
|
||||
|
||||
tokenId = request.args['tokenId'][0]
|
||||
tokenString = request.args['token'][0]
|
||||
mxId = request.args['mxId'][0]
|
||||
|
||||
sgassoc = self.sydent.validators.email.validateToken(tokenId, None, tokenString, mxId)
|
||||
|
||||
if not sgassoc:
|
||||
sgassoc = {'success':False}
|
||||
|
||||
return json.dumps(sgassoc).encode("UTF-8")
|
||||
|
||||
def render_OPTIONS(self, request):
|
||||
send_cors(request)
|
||||
request.setResponseCode(200)
|
||||
return "{}".encode("UTF-8")
|
52
sydent/http/servlets/lookupservlet.py
Normal file
52
sydent/http/servlets/lookupservlet.py
Normal file
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
import json
|
||||
|
||||
from sydent.util import validationutils
|
||||
|
||||
class LookupServlet(Resource):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
def render_GET(self, request):
|
||||
if not 'medium' in request.args or not 'address' in request.args:
|
||||
request.setResponseCode(400)
|
||||
resp = {'error': 'badrequest', 'message': "'medium' and 'address' fields are required"}
|
||||
return json.dumps(resp)
|
||||
|
||||
medium = request.args['medium'][0]
|
||||
address = request.args['address'][0]
|
||||
|
||||
cur = self.sydent.db.cursor()
|
||||
|
||||
# sqlite's support for upserts is atrocious but this is temporary anyway
|
||||
res = cur.execute("select mxId,createdAt,expires from threepid_associations "+
|
||||
"where medium = ? and address = ?", (medium, address))
|
||||
row = res.fetchone()
|
||||
if not row:
|
||||
return json.dumps({})
|
||||
|
||||
mxId = row[0]
|
||||
created = row[1]
|
||||
expires = row[2]
|
||||
|
||||
sgassoc = validationutils.signedThreePidAssociation(self.sydent, medium, address, mxId, created, expires)
|
||||
return json.dumps(sgassoc)
|
32
sydent/http/servlets/pubkeyservlets.py
Normal file
32
sydent/http/servlets/pubkeyservlets.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
import json
|
||||
import nacl.encoding
|
||||
|
||||
class Ed25519Servlet(Resource):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
def render_GET(self, request):
|
||||
pubKey = self.sydent.signers.ed25519.signing_key.verify_key
|
||||
pubKeyHex = pubKey.encode(encoder=nacl.encoding.HexEncoder)
|
||||
|
||||
return json.dumps({'public_key':pubKeyHex})
|
0
sydent/sign/__init__.py
Normal file
0
sydent/sign/__init__.py
Normal file
39
sydent/sign/ed25519.py
Normal file
39
sydent/sign/ed25519.py
Normal file
@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import nacl.encoding
|
||||
import nacl.signing
|
||||
import nacl.exceptions
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SydentEd25519:
|
||||
def __init__(self, syd):
|
||||
self.sydent = syd
|
||||
|
||||
skHex = self.sydent.cfg.get('crypto', 'ed25519.signingkey')
|
||||
if skHex != '':
|
||||
self.signing_key = nacl.signing.SigningKey(skHex, encoder=nacl.encoding.HexEncoder)
|
||||
else:
|
||||
logger.info("This server does not yet have an ed25519 signing key. "+
|
||||
"Creating one and saving it in the config file.")
|
||||
|
||||
self.signing_key = nacl.signing.SigningKey.generate()
|
||||
skHex = self.signing_key.encode(encoder=nacl.encoding.HexEncoder)
|
||||
self.sydent.cfg.set('crypto', 'ed25519.signingkey', skHex)
|
||||
self.sydent.save_config()
|
115
sydent/sydent.py
Normal file
115
sydent/sydent.py
Normal file
@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#from twisted.enterprise import adbapi
|
||||
|
||||
import ConfigParser
|
||||
import logging
|
||||
import os
|
||||
|
||||
from db.sqlitedb import SqliteDatabase
|
||||
|
||||
from http.httpserver import HttpServer
|
||||
from validators.emailvalidator import EmailValidator
|
||||
|
||||
from sign.ed25519 import SydentEd25519
|
||||
|
||||
from http.servlets.emailservlet import EmailRequestCodeServlet, EmailValidateCodeServlet
|
||||
from http.servlets.lookupservlet import LookupServlet
|
||||
from http.servlets.pubkeyservlets import Ed25519Servlet
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class Sydent:
|
||||
CONFIG_SECTIONS = ['general', 'db', 'http', 'email', 'crypto']
|
||||
CONFIG_DEFAULTS = {
|
||||
'server.name' : '',
|
||||
'db.file': 'sydent.sqlite',
|
||||
'token.length': '6',
|
||||
'http.port': '8090',
|
||||
'email.template': 'res/email.template',
|
||||
'email.from': 'Sydent Validation <noreply@{hostname}>',
|
||||
'email.subject': 'Your Validation Token',
|
||||
'email.smtphost': 'localhost',
|
||||
'log.path':'',
|
||||
'ed25519.signingkey':''
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
logger.info("Starting Sydent server")
|
||||
self.parse_config()
|
||||
|
||||
logPath = self.cfg.get('general', "log.path")
|
||||
if logPath != '':
|
||||
logging.basicConfig(level=logging.INFO, filename=logPath)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO, filename=logPath)
|
||||
|
||||
|
||||
self.db = SqliteDatabase(self).db
|
||||
|
||||
self.server_name = self.cfg.get('general', 'server.name')
|
||||
if self.server_name == '':
|
||||
self.server_name = os.uname()[1]
|
||||
logger.warn(("You had not specified a server name. I have guessed that this server is called '%s' "
|
||||
+" and saved this in the config file. If this is incorrect, you should edit server.name in "
|
||||
+"the config file.") % (self.server_name))
|
||||
self.cfg.set('general', 'server.name', self.server_name)
|
||||
self.save_config()
|
||||
|
||||
self.validators = Validators()
|
||||
self.validators.email = EmailValidator(self)
|
||||
|
||||
self.keyring = Keyring()
|
||||
self.keyring.ed25519 = SydentEd25519(self).signing_key
|
||||
|
||||
self.servlets = Servlets()
|
||||
self.servlets.emailRequestCode = EmailRequestCodeServlet(self)
|
||||
self.servlets.emailValidate = EmailValidateCodeServlet(self)
|
||||
self.servlets.lookup = LookupServlet(self)
|
||||
self.servlets.pubkey_ed25519 = Ed25519Servlet(self)
|
||||
|
||||
self.httpServer = HttpServer(self)
|
||||
|
||||
def parse_config(self):
|
||||
self.cfg = ConfigParser.SafeConfigParser(Sydent.CONFIG_DEFAULTS)
|
||||
for sect in Sydent.CONFIG_SECTIONS:
|
||||
try:
|
||||
self.cfg.add_section(sect)
|
||||
except ConfigParser.DuplicateSectionError:
|
||||
pass
|
||||
self.cfg.read("sydent.conf")
|
||||
|
||||
def save_config(self):
|
||||
fp = open("sydent.conf", 'w')
|
||||
self.cfg.write(fp)
|
||||
fp.close()
|
||||
|
||||
def run(self):
|
||||
self.httpServer.run()
|
||||
|
||||
class Validators:
|
||||
pass
|
||||
|
||||
class Servlets:
|
||||
pass
|
||||
|
||||
class Keyring:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
syd = Sydent()
|
||||
syd.run()
|
0
sydent/util/__init__.py
Normal file
0
sydent/util/__init__.py
Normal file
21
sydent/util/tokenutils.py
Normal file
21
sydent/util/tokenutils.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import string
|
||||
import random
|
||||
|
||||
def generateNumericTokenOfLength(length):
|
||||
return "".join([random.choice(string.digits) for _ in range(length)])
|
35
sydent/util/validationutils.py
Normal file
35
sydent/util/validationutils.py
Normal file
@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import syutil.crypto.jsonsign
|
||||
|
||||
|
||||
def signedThreePidAssociation(sydent, medium, address, mxId, not_before, not_after):
|
||||
sgassoc = { 'medium' : medium,
|
||||
'address' : address,
|
||||
'mxid' : mxId,
|
||||
'not_before':not_before,
|
||||
'not_after':not_after
|
||||
}
|
||||
sgassoc = syutil.jsonsign.sign_json(sgassoc, sydent.server_name, sydent.keyring.ed25519)
|
||||
return sgassoc
|
||||
|
||||
|
||||
def verifyThreePidAssociationFromHere(sydent, sgassoc):
|
||||
"""
|
||||
Verifies that this association signature is from *this* server
|
||||
"""
|
||||
syutil.jsonsign.verify_signed_json(sgassoc, sydent.server_name, sydent.keyring.ed25519)
|
26
sydent/validators/__init__.py
Normal file
26
sydent/validators/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
class Token:
|
||||
def __init__(self, _tokenId, _medium, _address, _tokenString, _clientSecret, _validated, _createdAt):
|
||||
self.tokenId = _tokenId
|
||||
self.medium = _medium
|
||||
self.address = _address
|
||||
self.tokenString = _tokenString
|
||||
self.clientSecret = _clientSecret
|
||||
self.validated = _validated
|
||||
self.createdAt = _createdAt
|
123
sydent/validators/emailvalidator.py
Normal file
123
sydent/validators/emailvalidator.py
Normal file
@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014 matrix.org
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sydent.util.tokenutils
|
||||
import smtplib
|
||||
import os
|
||||
import email.utils
|
||||
import logging
|
||||
import twisted.python.log
|
||||
import time
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
from sydent.db.threepidtoken import ThreePidTokenStore
|
||||
|
||||
from sydent.util import validationutils
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EmailValidator:
|
||||
THREEPID_ASSOCIATION_LIFETIME_MS = 100 * 365 * 24 * 60 * 60 * 1000
|
||||
|
||||
def __init__(self, sydent):
|
||||
self.sydent = sydent
|
||||
|
||||
def requestToken(self, emailAddress, clientSecret):
|
||||
tokenString = sydent.util.tokenutils.generateNumericTokenOfLength(
|
||||
int(self.sydent.cfg.get('email', 'token.length')))
|
||||
|
||||
myHostname = os.uname()[1]
|
||||
|
||||
mailFrom = self.sydent.cfg.get('email', 'email.from').format(hostname=myHostname)
|
||||
mailTo = emailAddress
|
||||
|
||||
mailTemplateFile = self.sydent.cfg.get('email', 'email.template')
|
||||
|
||||
mailString = open(mailTemplateFile).read().format(token=tokenString)
|
||||
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['Subject'] = self.sydent.cfg.get('email', 'email.subject')
|
||||
msg['From'] = mailFrom
|
||||
msg['To'] = mailTo
|
||||
|
||||
plainPart = MIMEText(mailString)
|
||||
msg.attach(plainPart)
|
||||
|
||||
rawFrom = email.utils.parseaddr(mailFrom)[1]
|
||||
rawTo = email.utils.parseaddr(mailTo)[1]
|
||||
|
||||
if rawFrom == '' or rawTo == '':
|
||||
logger.info("Couldn't parse from / to address %s / %s", mailFrom, mailTo)
|
||||
raise EmailAddressException()
|
||||
|
||||
mailServer = self.sydent.cfg.get('email', 'email.smtphost')
|
||||
|
||||
logger.info("Attempting to mail code %s to %s using mail server %s", tokenString, rawTo, mailServer)
|
||||
|
||||
try:
|
||||
smtp = smtplib.SMTP(mailServer)
|
||||
smtp.sendmail(rawFrom, rawTo, msg.as_string())
|
||||
smtp.quit()
|
||||
except Exception as origException:
|
||||
twisted.python.log.err()
|
||||
ese = EmailSendException()
|
||||
ese.cause = origException
|
||||
raise ese
|
||||
|
||||
threePidStore = ThreePidTokenStore(self.sydent)
|
||||
createdMs = int(time.time() * 1000.0)
|
||||
tokenId = threePidStore.addToken('email', rawTo, tokenString, clientSecret, createdMs)
|
||||
|
||||
return tokenId
|
||||
|
||||
def validateToken(self, tokenId, clientSecret, token, mxId):
|
||||
"""
|
||||
XXX: This also binds the validated 3pid to an mxId so the structure needs a rethink.
|
||||
"""
|
||||
threePidStore = ThreePidTokenStore(self.sydent)
|
||||
tokenObj = threePidStore.getTokenById(tokenId)
|
||||
if not tokenObj:
|
||||
return False
|
||||
|
||||
# TODO once we can validate the token oob
|
||||
#if tokenObj.validated and clientSecret == tokenObj.clientSecret:
|
||||
# return True
|
||||
|
||||
if tokenObj.tokenString == token:
|
||||
createdAt = int(time.time() * 1000.0)
|
||||
expires = createdAt + EmailValidator.THREEPID_ASSOCIATION_LIFETIME_MS
|
||||
|
||||
cur = self.sydent.db.cursor()
|
||||
|
||||
# sqlite's support for upserts is atrocious but this is temporary anyway
|
||||
cur.execute("insert or replace into threepid_associations ('medium', 'address', 'mxId', 'createdAt', 'expires')"+
|
||||
" values (?, ?, ?, ?, ?)",
|
||||
(tokenObj.medium, tokenObj.address, mxId, createdAt, expires))
|
||||
self.sydent.db.commit()
|
||||
|
||||
return validationutils.signedThreePidAssociation(self.sydent, tokenObj.medium, tokenObj.address,
|
||||
createdAt, expires, mxId)
|
||||
else:
|
||||
return False
|
||||
|
||||
class EmailAddressException(Exception):
|
||||
pass
|
||||
|
||||
class EmailSendException(Exception):
|
||||
pass
|
Loading…
x
Reference in New Issue
Block a user