Example #1
0
    def test_report_success(self):
        record = Record(student_id=self.student_id)
        record.state = Record.LOCKED
        record.save()
        vote_token = AuthToken.generate(self.student_id, str(self.station.external_id), '70')
        vote_token.save()

        url = reverse('report')
        data = {
            'uid': self.student_id,
            'vote_token': vote_token.code,
            'token': self.token,
            'api_key': settings.API_KEY, 'version': settings.API_VERSION,
        }
        response = self.client.post(url, data)
        self.assertEqual(response.data, {'status': 'success'})
Example #2
0
    def test_complete_success(self):
        record = Record(student_id=self.student_id)
        record.state = Record.VOTING
        record.save()
        token = AuthToken.generate(self.student_id, str(self.station.external_id), '70')
        token.save()

        url = 'https://{0}{1}?callback={2}'.format(
            settings.CALLBACK_DOMAIN, reverse('callback'), token.confirm_code)
        response = self.client.get(url)
        record = Record.objects.get(student_id=self.student_id)
        self.assertEqual(record.state, Record.USED)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, {
            'status': 'success',
            'message': 'all correct',
        })
Example #3
0
    def test_confirm_success(self):
        record = Record(student_id=self.student_id)
        record.state = Record.LOCKED
        record.save()
        vote_token = AuthToken.generate(self.student_id, str(self.station.external_id), '70')
        vote_token.save()

        url = reverse('confirm')
        data = {'uid': self.student_id,
                'vote_token': vote_token.code, 'token': self.token,
                'api_key': settings.API_KEY, 'version': settings.API_VERSION}
        response = self.client.post(url, data)
        callback = 'https://{0}{1}?callback={2}'.format(
            settings.CALLBACK_DOMAIN, reverse('callback'), vote_token.confirm_code)
        self.assertEqual(response.data, {
            'status': 'success',
            'ballot': self.authcode.code,
            'callback': callback,
        })
def authenticate(request):
    # Check parameters
    internal_id = request.data['cid']
    raw_student_id = request.data['uid']
    station_id = request.station

    if settings.ENFORCE_CARD_VALIDATION:
        # Parse student ID
        if re.match(r'[A-Z]\d{2}[0-9A-Z]\d{6}', raw_student_id) and re.match(r'[0-9a-f]{8}', internal_id) and re.match(r'\d+', station_id):
            # Extract parameters
            student_id = raw_student_id[:-1]
            revision = int(raw_student_id[-1:])
            logger.info('Station %s request for card %s[%s]', station_id, student_id, revision)
        else:
            # Malformed card information
            logger.info('Station %s request for card %s (%s)', station_id, raw_student_id, internal_id)
            return error('card_invalid')

    else:
        # Do not reveal full internal ID as ACA requested
        logger.info('Station %s request for card (%s****)', station_id, internal_id[:4])

    # Call ACA API
    try:
        aca_info = service.to_student_id(internal_id)

    except URLError:
        logger.exception('Failed to connect to ACA server')
        return error('external_error', status.HTTP_502_BAD_GATEWAY)

    except service.ExternalError as e:
        if not settings.ENFORCE_CARD_VALIDATION:
            # We can only reveal full internal ID if it’s an invalid card
            logger.exception('Card rejected by ACA server (%s), reason %s', internal_id, e.reason)
        else:
            logger.exception('Card rejected by ACA server, reason %s', e.reason)

        # Tell clients the exact reason of error
        if e.reason == 'card_invalid' or e.reason == 'student_not_found':
            return error('card_invalid')
        elif e.reason == 'card_blacklisted':
            return error('card_suspicious')
        return error('external_error', status.HTTP_502_BAD_GATEWAY)

    else:
        if settings.ENFORCE_CARD_VALIDATION:
            student_id = aca_info.id
            revision = 0
            logger.info('User %s (%s) checked', student_id, aca_info.type)
        elif aca_info.id != student_id:
            logger.info('ID %s returned instead', aca_info.id)
            return error('card_suspicious')

    # Check vote record
    try:
        record = Record.objects.get(student_id=student_id)
        if settings.ENFORCE_CARD_VALIDATION and record.revision != revision:
            # ACA claim the card valid!
            logger.info('Expect revision %s, recorded %s', revision, record.revision)
            return error('card_suspicious')

        if record.state == Record.VOTING:
            # Automaticlly unlock stuck record
            record.state = Record.AVAILABLE
            record.save()
            logger.info('Reset %s state from VOTING', student_id)

        if record.state != Record.AVAILABLE:
            logger.error('Duplicate entry (%s)', student_id)
            return error('duplicate_entry')

    except Record.DoesNotExist:
        record = Record(student_id=student_id, revision=revision)

    # Build up kind identifier
    try:
        college = settings.COLLEGE_IDS[aca_info.college]
    except KeyError:
        # Use student ID as an alternative
        logger.warning('No matching college for ACA entry %s', aca_info.college)
        college = student_id[3]

        # In rare cases, we may encounter students without colleges
        if college not in settings.COLLEGE_NAMES:
            logger.warning('No matching college for ID %s', college)
            college = '0'

    # Determine graduate status
    type_code = student_id[0]
    kind = college
    try:
        override = OverrideEntry.objects.get(student_id=student_id)
        kind = override.kind
    except OverrideEntry.DoesNotExist:
        if type_code in settings.GRADUATE_CODES:
            if aca_info.department in settings.JOINT_DEPARTMENT_CODES:
                kind += 'B'
            else:
                kind += '1'
        elif type_code in settings.UNDERGRADUATE_CODES:
            if aca_info.department in settings.JOINT_DEPARTMENT_CODES:
                kind += 'A'
            else:
                kind += '0'

    # Check if student has eligible identity
    if kind not in settings.KINDS:
        return error('unqualified')

    # Generate record and token
    record.state = Record.LOCKED
    record.save()

    token = AuthToken.generate(student_id, station_id, kind)
    token.save()

    logger.info('Auth token issued: %s', token.code)
    return Response({'status': 'success', 'uid': student_id, 'type': settings.KINDS[kind], 'vote_token': token.code})
def authenticate(request):
    # Check parameters
    internal_id = request.data['cid']
    raw_student_id = request.data['uid']
    station_id = request.station

    if settings.ENFORCE_CARD_VALIDATION:
        # Parse student ID
        if re.match(r'[A-Z]\d{2}[0-9A-Z]\d{6}', raw_student_id) and re.match(r'[0-9a-f]{8}', internal_id) and re.match(r'\d+', station_id):
            # Extract parameters
            student_id = raw_student_id[:-1]
            revision = int(raw_student_id[-1:])
            logger.info('Station %s request for card %s[%s]', station_id, student_id, revision)
        else:
            # Malformed card information
            logger.info('Station %s request for card %s (%s)', station_id, raw_student_id, internal_id)
            return error('card_invalid')

    else:
        # Do not reveal full internal ID as ACA requested
        logger.info('Station %s request for card (%s****)', station_id, internal_id[:4])

    # Call ACA API
    try:
        aca_info = service.to_student_id(internal_id)

    except URLError:
        logger.exception('Failed to connect to ACA server')
        return error('external_error', status.HTTP_502_BAD_GATEWAY)

    except service.ExternalError as e:
        if not settings.ENFORCE_CARD_VALIDATION:
            # We can only reveal full internal ID if it’s an invalid card
            logger.exception('Card rejected by ACA server (%s), reason %s', internal_id, e.reason)
        else:
            logger.exception('Card rejected by ACA server, reason %s', e.reason)

        # Tell clients the exact reason of error
        if e.reason == 'card_invalid' or e.reason == 'student_not_found':
            return error('card_invalid')
        elif e.reason == 'card_blacklisted':
            return error('card_suspicious')
        return error('external_error', status.HTTP_502_BAD_GATEWAY)

    else:
        if not settings.ENFORCE_CARD_VALIDATION:
            student_id = aca_info.id
            revision = 0
            logger.info('User %s (%s) checked', student_id, aca_info.type)
        elif aca_info.id != student_id:
            logger.info('ID %s returned instead', aca_info.id)
            return error('card_suspicious')

    # Check vote record
    try:
        record = Record.objects.get(student_id=student_id)
        if settings.ENFORCE_CARD_VALIDATION and record.revision != revision:
            # ACA claim the card valid!
            logger.info('Expect revision %s, recorded %s', revision, record.revision)
            return error('card_suspicious')

        if record.state == Record.VOTING:
            # Automaticlly unlock stuck record
            record.state = Record.AVAILABLE
            record.save()
            logger.info('Reset %s state from VOTING', student_id)

        if record.state != Record.AVAILABLE:
            logger.error('Duplicate entry (%s)', student_id)
            return error('duplicate_entry')

    except Record.DoesNotExist:
        record = Record(student_id=student_id, revision=revision)

    # Determine graduate status
    kind = kind_classifier.get_kind(aca_info)

    if kind is None:
        return error('unqualified')

    # Generate record and token
    record.state = Record.LOCKED
    record.save()

    token = AuthToken.generate(student_id, station_id, kind)
    token.save()

    logger.info('Auth token issued: %s', token.code)
    return Response({'status': 'success', 'uid': student_id, 'type': aca_info.college, 
        'college': settings.DPTCODE_NAME[aca_info.department], 'vote_token': token.code})