176 lines
6.2 KiB
Python
176 lines
6.2 KiB
Python
import hashlib
|
|
import json
|
|
import logging
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponse
|
|
from django.views.decorators.http import require_POST
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from foldit.models import Score, PuzzleComplete
|
|
from student.models import unique_id_for_user
|
|
|
|
import re
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@login_required
|
|
@csrf_exempt
|
|
@require_POST
|
|
def foldit_ops(request):
|
|
"""
|
|
Endpoint view for foldit operations.
|
|
"""
|
|
responses = []
|
|
if "SetPlayerPuzzleScores" in request.POST:
|
|
puzzle_scores_json = request.POST.get("SetPlayerPuzzleScores")
|
|
pz_verify_json = request.POST.get("SetPlayerPuzzleScoresVerify")
|
|
log.debug("SetPlayerPuzzleScores message: puzzle scores: %r",
|
|
puzzle_scores_json)
|
|
|
|
puzzle_score_verify = json.loads(pz_verify_json)
|
|
if not verifies_ok(request.user.email,
|
|
puzzle_scores_json, puzzle_score_verify):
|
|
responses.append({"OperationID": "SetPlayerPuzzleScores",
|
|
"Success": "false",
|
|
"ErrorString": "Verification failed",
|
|
"ErrorCode": "VerifyFailed"})
|
|
log.warning(
|
|
"Verification of SetPlayerPuzzleScores failed:"
|
|
"user %s, scores json %r, verify %r",
|
|
request.user,
|
|
puzzle_scores_json,
|
|
pz_verify_json
|
|
)
|
|
else:
|
|
# This is needed because we are not getting valid json - the
|
|
# value of ScoreType is an unquoted string. Right now regexes are
|
|
# quoting the string, but ideally the json itself would be fixed.
|
|
# To allow for fixes without breaking this, the regex should only
|
|
# match unquoted strings,
|
|
a = re.compile(r':([a-zA-Z]*),')
|
|
puzzle_scores_json = re.sub(a, r':"\g<1>",', puzzle_scores_json)
|
|
puzzle_scores = json.loads(puzzle_scores_json)
|
|
responses.append(save_scores(request.user, puzzle_scores))
|
|
|
|
if "SetPuzzlesComplete" in request.POST:
|
|
puzzles_complete_json = request.POST.get("SetPuzzlesComplete")
|
|
pc_verify_json = request.POST.get("SetPuzzlesCompleteVerify")
|
|
|
|
log.debug("SetPuzzlesComplete message: %r",
|
|
puzzles_complete_json)
|
|
|
|
puzzles_complete_verify = json.loads(pc_verify_json)
|
|
|
|
if not verifies_ok(request.user.email,
|
|
puzzles_complete_json, puzzles_complete_verify):
|
|
responses.append({"OperationID": "SetPuzzlesComplete",
|
|
"Success": "false",
|
|
"ErrorString": "Verification failed",
|
|
"ErrorCode": "VerifyFailed"})
|
|
log.warning(
|
|
"Verification of SetPuzzlesComplete failed:"
|
|
" user %s, puzzles json %r, verify %r",
|
|
request.user,
|
|
puzzles_complete_json,
|
|
pc_verify_json
|
|
)
|
|
else:
|
|
puzzles_complete = json.loads(puzzles_complete_json)
|
|
responses.append(save_complete(request.user, puzzles_complete))
|
|
|
|
return HttpResponse(json.dumps(responses))
|
|
|
|
|
|
def verify_code(email, val):
|
|
"""
|
|
Given the email and passed in value (str), return the expected
|
|
verification code.
|
|
"""
|
|
# TODO: is this the right string?
|
|
verification_string = email.lower() + '|' + val
|
|
return hashlib.md5(verification_string).hexdigest()
|
|
|
|
|
|
def verifies_ok(email, val, verification):
|
|
"""
|
|
Check that the hash_str matches the expected hash of val.
|
|
|
|
Returns True if verification ok, False otherwise
|
|
"""
|
|
if verification.get("VerifyMethod") != "FoldItVerify":
|
|
log.debug("VerificationMethod in %r isn't FoldItVerify", verification)
|
|
return False
|
|
hash_str = verification.get("Verify")
|
|
|
|
return verify_code(email, val) == hash_str
|
|
|
|
|
|
def save_scores(user, puzzle_scores):
|
|
score_responses = []
|
|
for score in puzzle_scores:
|
|
log.debug("score: %s", score)
|
|
# expected keys ScoreType, PuzzleID (int),
|
|
# BestScore (energy), CurrentScore (Energy), ScoreVersion (int)
|
|
|
|
puzzle_id = score['PuzzleID']
|
|
best_score = score['BestScore']
|
|
current_score = score['CurrentScore']
|
|
score_version = score['ScoreVersion']
|
|
|
|
# SetPlayerPuzzleScoreResponse object
|
|
# Score entries are unique on user/unique_user_id/puzzle_id/score_version
|
|
try:
|
|
obj = Score.objects.get(
|
|
user=user,
|
|
unique_user_id=unique_id_for_user(user),
|
|
puzzle_id=puzzle_id,
|
|
score_version=score_version)
|
|
obj.current_score = current_score
|
|
obj.best_score = best_score
|
|
|
|
except Score.DoesNotExist:
|
|
obj = Score(
|
|
user=user,
|
|
unique_user_id=unique_id_for_user(user),
|
|
puzzle_id=puzzle_id,
|
|
current_score=current_score,
|
|
best_score=best_score,
|
|
score_version=score_version)
|
|
obj.save()
|
|
|
|
score_responses.append({'PuzzleID': puzzle_id,
|
|
'Status': 'Success'})
|
|
|
|
return {"OperationID": "SetPlayerPuzzleScores", "Value": score_responses}
|
|
|
|
|
|
def save_complete(user, puzzles_complete):
|
|
"""
|
|
Returned list of PuzzleIDs should be in sorted order (I don't think client
|
|
cares, but tests do)
|
|
"""
|
|
for complete in puzzles_complete:
|
|
log.debug("Puzzle complete: %s", complete)
|
|
puzzle_id = complete['PuzzleID']
|
|
puzzle_set = complete['Set']
|
|
puzzle_subset = complete['SubSet']
|
|
|
|
# create if not there
|
|
PuzzleComplete.objects.get_or_create(
|
|
user=user,
|
|
unique_user_id=unique_id_for_user(user),
|
|
puzzle_id=puzzle_id,
|
|
puzzle_set=puzzle_set,
|
|
puzzle_subset=puzzle_subset)
|
|
|
|
# List of all puzzle ids of intro-level puzzles completed ever, including on this
|
|
# request
|
|
# TODO: this is just in this request...
|
|
|
|
complete_responses = list(pc.puzzle_id
|
|
for pc in PuzzleComplete.objects.filter(user=user))
|
|
|
|
return {"OperationID": "SetPuzzlesComplete", "Value": complete_responses}
|