Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Implementing get routes #15

Open
wants to merge 55 commits into
base: Unautenticated_With_JS
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
64e342e
Added two routes: GET /readings and GET /login
CSDUMMI Feb 22, 2020
bf741b4
Simplified messages: We only allow public posts from now on.
CSDUMMI Feb 23, 2020
7f6e5e7
GET /read Route implemented.
CSDUMMI Feb 23, 2020
292e602
Added Feed creation to GET / route:
CSDUMMI Feb 23, 2020
3c9c3dc
Added upload_time as a simple increasing integer on every new message.
CSDUMMI Feb 24, 2020
e7998ec
United GET /login and POST / into a single function - Which fixes a bug.
CSDUMMI Feb 24, 2020
62f3591
Fixed syntax bug.
CSDUMMI Feb 24, 2020
0326b8d
Import session
CSDUMMI Feb 24, 2020
a8a070a
Imported redirect.
CSDUMMI Feb 24, 2020
e371f9c
Turned response codes into strings.
CSDUMMI Feb 24, 2020
b68d2ad
If invalid username or password,
CSDUMMI Feb 24, 2020
55a431b
Using try except else to catch errors.
CSDUMMI Feb 25, 2020
4bfc7e1
Removed restrictions on /read.
CSDUMMI Feb 25, 2020
fdf9ba5
Made Feed publicly accessible
CSDUMMI Feb 25, 2020
3a97a09
Made errors into a dictionary,
CSDUMMI Feb 25, 2020
5d8bc7c
Using errors dictionary.
CSDUMMI Feb 25, 2020
ec7a458
Consistent use of errors and good raising of errors.
CSDUMMI Feb 25, 2020
0b2a94d
Using sorting functions in python instead of mongodb ones.
CSDUMMI Feb 25, 2020
ff48836
global client and collection
CSDUMMI Feb 25, 2020
c5f92b7
Removed no longer needed parts of Users.publish
CSDUMMI Feb 25, 2020
cdd7240
Added writings route.
CSDUMMI Feb 25, 2020
001e1c1
GET /vote
CSDUMMI Feb 26, 2020
6212e56
Adding Exception classes.
CSDUMMI Feb 26, 2020
d9aeb2e
Simple implementation of read.html and index.html - The data is inclu…
CSDUMMI Feb 26, 2020
965c311
Trying to implement register() function and changing date format to i…
CSDUMMI Feb 26, 2020
4baa2af
Trying to implement register() function and changing date format to i…
CSDUMMI Feb 26, 2020
5866632
Merge branch 'Implementing_GET_Routes' of https://github.com/CSDUMMI/…
CSDUMMI Feb 26, 2020
b2d080a
Added implementation of Cryptographic functions,
CSDUMMI Feb 26, 2020
8faff01
Exception handeling in Crypto.
CSDUMMI Feb 26, 2020
40fcc8e
Added inverse function to encrypt, decrypt.
CSDUMMI Feb 26, 2020
19c53ce
Added GET /register and POST /register to create a new user, which is…
CSDUMMI Feb 26, 2020
9d9b82e
Converting Hash objects to strings.
CSDUMMI Feb 26, 2020
3d22ca6
Improved output/login.html.
CSDUMMI Feb 26, 2020
8c2e749
New Error subclass.
CSDUMMI Feb 26, 2020
e1c3e51
Implementing voting.
CSDUMMI Feb 26, 2020
8bd011d
Unifying error catching in vote().
CSDUMMI Feb 26, 2020
80c8ffd
Added writing hash to write() function as parameter.
CSDUMMI Feb 27, 2020
85e7626
GET /write and POST /write implemented
CSDUMMI Feb 27, 2020
20ea297
POST /write added to make it possible to store what has been written.
CSDUMMI Feb 27, 2020
61dc5f1
redirecting to GET /write/<hash>
CSDUMMI Feb 27, 2020
73a1f67
Defined the three step life of a vote.
CSDUMMI Feb 27, 2020
4c7505a
Added route /voting/option/<vote_id> to create an option to a vote. B…
CSDUMMI Feb 27, 2020
acb9e97
Made Feed only list none-drafts.
CSDUMMI Feb 28, 2020
9eab180
Implementing part of voting/option/<vote_id>
CSDUMMI Feb 28, 2020
9c9b248
Cut losses and don't use dictionaries as arguments, if all the fields…
CSDUMMI Feb 28, 2020
6e771ff
Fixed mistake in calling SHA256.new(), providing data as positional a…
CSDUMMI Feb 28, 2020
f05cb3f
Created type checks function and module.
CSDUMMI Feb 28, 2020
22b4eda
Type checking of input to /voting/option/<vote_id>
CSDUMMI Feb 28, 2020
e81b54a
Changed check_dictionary_for_type to check_for_type.
CSDUMMI Feb 28, 2020
65ce131
Put assertions in Type_checks.py into a loop.
CSDUMMI Feb 28, 2020
89a8300
Pushing the newly created option to the options field in the election…
CSDUMMI Feb 28, 2020
869ed56
path_of_patch to create the proper path to PATCH directory and check …
CSDUMMI Feb 28, 2020
ff4cdb5
Made check of existence in path_of_patch optional.
CSDUMMI Feb 28, 2020
a4f57a4
/voting/option/<vote_id> can now take a patch as an option and uses t…
CSDUMMI Feb 28, 2020
dfd3ef0
Stop. This is going to be merged and later continued with a simpler a…
CSDUMMI Mar 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions Database.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,20 @@ A Law Document looks like this:
```

# users
For every Registered user there is a
Public and Private PGP Key (GnuPG):
For every Registered user there is a private and public key.
The private key is encrypted with each of the users three passwords.
This makes it possible to use private keys with each of them
```json
{ "public_key" : "<RSA Public Key>"
, "private_key" : "<Encrypted RSA Private Key>"
, "private_keys" : ["<Encrypted RSA Private Key>"]
, "passwords" : ["<passwords>"]
, "username" : "<unique username>"
, "first_name" : "<first real name>"
, "last_name" : "<last real name>"
, "expiration" : "<date of expiration of key pair>"
, "feed" : ["<hash of message where this user is either in the to or from part>"]
, "readings" : ["<hash of messages, that the user is reading>"]
, "writings" : ["<hash of messages, that the user is writing>"]
, "old_keys" : [{ "expiration" : "<unix timestamp of their expiration>"
, "public_key" :"<old public key until the expiration>"
, "private_key" : "<old, encrypted private key>"
Expand All @@ -115,24 +119,17 @@ Public and Private PGP Key (GnuPG):
```

# messages
Anybody can send end-to-end encypted messages to anyone.
A message, that is addressed to `all` is a post.
The process of encrypting and decrypting a message
from Alice to Bob is like this:

Alice's Message -> Alice's Private Key -> Bob's Public Key -> Send -> Bob's Private Key -> Alice's Public Key

A post is just signed by the author

Alice's Message -> Alice's Private Key -> Published

For now, there can be only one kind of messages:
Public posts.
These messages can be read and are addressed to anyone.
```json
{ "from" : "<username of the author>"
, "to" : [ "<username's of the recipients or 'all', when the message is a post>" ]
, "body" : [{ "recipient_name" : "<username of the recipient>"
, "ciphertext" : "<encrypted text of the message, D(D(body, recipient_private_key), author_public_key) == message>"
}]
, "to" : "all"
, "body" : { "title" : "<title of the post"
, "content" : "<markdown content of the post."
}
, "hash" : "<SHA256 of a dict of all the other fields in the message, used as unique and secure identifier>"
, "draft" : <true if message is a draft and not yet ready to be send.>
}
```
If in "to" there is `"all"` then the body isn't encrypted
Expand Down
88 changes: 30 additions & 58 deletions Server/Patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,27 @@
import subprocess, os, json
from Crypto.Hash import SHA256
from pymongo import MongoClient

"""
Env Variables:
$PATCHES : Folder with the Git Repositories of the Patches (format: patcher-patch/)
$ORIGIN_REPOSITORY : Location of the Original Git Repository, from which patches are cloned from
and to whom patches are pushed after being approved.

create a patch:
- clone the origin_repository, as stored in the $ORIGIN_REPOSITORY envvar.
- create an entry in the collection patches on the demnet database.

This patch needs the following options:

- The name of the patcher, that can either be the username in demnet
or a pseudonyme chosen by a non-user, which will then be noted in a separate
variable called "is_user".

- The name of the patch, that should be descriptive (hopefully)

- The simple_description of the patcher's intent.
This is useful for users, if the patcher wants to hold an election before
starting development. These users won't often be interested in the technical
details, for whom this description should suffice.

- The technical_description is for all developers trying to understand
the patcher's intent on a deeper level.

- As part of the technical_description, the patcher can also reference issues,
bug reports and generally link to ressources, detailing his intention.
This is separatly done in the "references" field.
The Patcher should put a most conclusive list here
and reference only some of it in the descriptions.

- The Patcher may not want to start developing, waste hours and hours of time,
and see that the users don't even have an interest their patch.
All patches have to pass an election eventually, but if the patcher wants to,
they can hold an election before starting to develop, just to see if there
is any interest. If the election is lost, the patcher will either have to delete
the patch or modify it substantially. If they want to hold such an election,
"hold_pre_election" must be set to True.

- After the Patch has been closed and the Patch Repository deleted, the
patch entry "closed" = True

After creating the patch,
a SHA256 of all the data is created and
returned as the reference to the patch.
This is also put into one field called "sha256".
(That this isn't included in the calculation of the hash is self-evident.)
"""
def create(patcher,patch_name, options):
from typing import List

def create ( patcher : str
, patch_name : str
, is_user : bool
, simple_description : str
, technical_description : str
, references : List[str]
):
client = MongoClient()
db = client.demnet
patches = db.patches
patch = { "patcher" : patcher
, "is_user" : options['is_user']
, "is_user" : is_user
, "name" : patch_name
, "simple_description" : options['simple_description']
, "technical_description" : options['technical_description']
, "hold_pre_election" : options['hold_pre_election']
, "references" : options['references']
, "simple_description" : simple_description
, "technical_description" : technical_description
, "references" : references
, "closed" : False
}
patch['hash'] = SHA256.new(data=json.dumps(patch).encode('utf-8')).hexdigest()
patch['hash'] = SHA256.new().update(json.dumps(patch)).encode('utf-8')).hexdigest()
patch_id = patches.insert_one(patch).inserted_id

subprocess.run(['bash', 'patch.sh', 'create', os.environ["ORIGIN_REPOSITORY"], patcher, patch_name])
Expand All @@ -75,6 +32,14 @@ def create(patcher,patch_name, options):
def merge(patcher, patch_name):
subprocess.run(['bash', 'patch.sh', 'merge', patcher, patch_name])

def lock(patcher, patch_name):
path_of_patch = path_of_patch(patcher,patch_name)
if not path_of_patch:
return False
else:
os.chmod(path_of_patch, 0444) # readonly to all
return True

"""
Closing a patch means:
Deleting the Git Repo.
Expand All @@ -90,7 +55,7 @@ def close(patcher, patch_name, id, merge=False):
, "closed" : False
} )

repo_path = f"{os.environ['PATCHES']}/{patcher}-{patch_name}"
repo_path = path_of_patch(patcher, patch_name, check_existence=False)
if patch and os.path.isdir(repo_path):
if merge:
merge(patcher, patch_name)
Expand All @@ -101,3 +66,10 @@ def close(patcher, patch_name, id, merge=False):
return True
else:
return False

def path_of_patch(patcher, patch_name, check_existence=True):
path = f"{os.environ["PATCHES"]}/{patcher}-{patch_name}"
if check_existence:
return path if os.path.isdir(path) else False
else:
return path
49 changes: 49 additions & 0 deletions Server/Type_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import Dict, TypeVar
class Type_Check_Error (Exception):
def __init__(self,data_name, msg):
self.data_name = data_name
self.msg = msg

Dict_Values = TypeVar("Dict_Values")
def check_for_type(dictionary_name : str, dictionary : Dict[str, Dict_Values]):
try:
if dictionary_name == "additions":
for addition in dictionary:
assert check_for_type(addition, { "title" : ""
, "book" : ""
, "articles" : []
}
)
elif dictionary == "amendments":
for amendment in dictionary:
assert check_for_type(amendment, { "book" : ""
, "law" : ""
, "articles" : []
}
)
elif dictionary == "repeals":
for repeal in repeals:
assert check_for_type(repael, { "books" : []
, "laws" : []
})
else:
raise Type_Check_Error(dictionary_name, "unknown dictionary name")
except AssertionError as assertion_error:
raise Type_Check_Error(dictionary_name, assertion_error.args[0])
except Exception as e:
raise e
else:
return True

def check_dictionary_for_type ( dictionary : Dict[str, Dict_Values]
, types : Dict[str, Dict_Values]
):
try:
tests = [(type(types[key]) == type(dictionary[key]), key) for key in types]
test_failed = False in filter(lambda t: t[0], tests)
assert not test_failed, f"Type Check Failed: {list(map(lambda t: t[1], filter(lambda t: t[0] == False, tests)))}"

except Exception as e:
raise e
else:
return True
27 changes: 10 additions & 17 deletions Server/Users.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from pymongo import MongoClient
import pymongo
import datetime, sys, json

client = MongoClient()
Expand All @@ -31,7 +32,7 @@ def login(username, passphrase):
if keys.publickey().export_key(format="PEM") != user["public_key"]:
users.update_one({ "username" : username }, { "$set" : { "public_key" : keys.publickey().export_key(format="PEM") } } )

if datetime.datetime.strptime(user["expiration"], "%m/%d/%Y") > datetime.datetime.now():
if datetime.date.fromisoformat(user["expiration"]) > datetime.datetime.now():

new_keys = RSA.generate(2048)
new_expiration = datetime.timedelta (weeks=104
Expand All @@ -43,8 +44,9 @@ def login(username, passphrase):
,microseconds=0
) + datetime.datetime.now()

private_key = new_keys.export_key(format="PEM", passphrase=passphrase)
public_key = new_keys.publickey().export_key(format="PEM")
new_expiration = new_expiration.isoformat()
private_key = new_keys.export_key(format="PEM", passphrase=passphrase)
public_key = new_keys.publickey().export_key(format="PEM")


users.update_one( { "username" : username }
Expand Down Expand Up @@ -129,30 +131,21 @@ def encrypt(message,keys):

"""
Publishing a message works in these steps:
1. Encrypt/Sign the message
2. Publish it in demnet.messages
3. Add a notification to the recipient's feed.
1. Publish it in demnet.messages
Parameters:
- *message* message document with unencrypted body
- *keys* private and public keys of the author
Returns:
True if successfull
False if not
"""
def publish(message, keys):
# 1. Encrypt/Sign the message
body = encrypt(message, keys)
if body != False:
def publish(message):
if message['body'] != False:
# 2. Publish it in demnet.messages
message['body'] = body
message['hash'] = SHA256.new(json.dumps(body).encode('utf-8')).hexdigest()
messages = db.messages
message['hash'] = SHA256.new(json.dumps(message['body']).encode('utf-8')).hexdigest()
message['upload_time'] = messages.find().sort("upload_time", pymongo.DESCENDING).limit(1)[0]["upload_time"] + 1 # Some inconsistent (race condition) is possible, but not critical.
messages.insert_one(message)

# 3. Add a notification to the recipient's feed
for recipient_name in message['to']:
users.update_one({ "username" : recipient_name }
, { "$push" : { "feed" : message["hash"] } }
)
return True
return False
Loading