connect_verify()   B
last analyzed

Complexity

Conditions 7

Size

Total Lines 62
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 43.4643

Importance

Changes 0
Metric Value
cc 7
eloc 46
nop 0
dl 0
loc 62
ccs 3
cts 32
cp 0.0938
crap 43.4643
rs 7.3672
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""
2
byceps.blueprints.site.connected_external_accounts.discord.views
3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5
Connector to Discord
6
7
:Copyright: 2021-2023 Jan Korneffel
8
:License: Revised BSD (see `LICENSE` file for details)
9
"""
10
11 1
from datetime import datetime
12 1
from urllib import parse
13
14 1
from flask import current_app, g, redirect, request, url_for
15 1
import requests
16
17 1
from byceps.services.connected_external_accounts import (
18
    connected_external_accounts_service,
19
)
20 1
from byceps.util.framework.blueprint import create_blueprint
21 1
from byceps.util.framework.flash import flash_error, flash_success
22 1
from byceps.util.views import login_required, redirect_to, respond_no_content
23
24
25 1
blueprint = create_blueprint('connected_external_accounts_discord', __name__)
26
27
28 1
API_URL_BASE = 'https://discord.com/api/v10'
29
30 1
SERVICE_NAME = 'discord'
31
32
33 1
@blueprint.get('/connect')
34 1
@login_required
35 1
def connect():
36
    """Connect account with Discord via OAuth2."""
37
    client_id = current_app.config.get('DISCORD_CLIENT_ID')
38
    if not client_id:
39
        flash_error('Verbindung mit Discord derzeit nicht möglich.')
40
        return redirect_to('user_settings.view')
41
42
    query_string_data = {
43
        'client_id': client_id,
44
        'redirect_uri': url_for('.connect_verify', _external=True),
45
        'response_type': 'code',
46
        'scope': 'identify',
47
    }
48
    query_string = parse.urlencode(query_string_data)
49
    auth_url = API_URL_BASE + '/oauth2/authorize?' + query_string
50
    return redirect(auth_url)
51
52
53 1
def error():
54
    flash_error('Verbindung mit Discord-Account fehlgeschlagen.')
55
    return redirect_to('user_settings.view')
56
57
58 1
@blueprint.get('/connect/verify')
59 1
@login_required
60 1
def connect_verify():
61
    """Verify signed Discord parameters."""
62
    params = request.args.to_dict()
63
64
    if 'code' not in params:
65
        return error()
66
67
    client_id = current_app.config.get('DISCORD_CLIENT_ID')
68
    client_secret = current_app.config.get('DISCORD_CLIENT_SECRET')
69
    if not client_id or not client_secret:
70
        flash_error('Verbindung mit Discord derzeit nicht möglich.')
71
        return redirect_to('user_settings.view')
72
73
    auth = (client_id, client_secret)
74
    token_request_data = {
75
        'scope': 'identify',
76
        'redirect_uri': url_for('.connect_verify', _external=True),
77
        'code': params['code'],
78
        'grant_type': 'authorization_code',
79
    }
80
81
    # Try to obtain access token with given authorization code.
82
    token_response = requests.post(
83
        API_URL_BASE + '/oauth2/token',
84
        auth=auth,
85
        data=token_request_data,
86
        timeout=10,
87
    )
88
    reponse_token = token_response.json()
89
    if not token_response.ok or ('access_token' not in reponse_token):
90
        return error()
91
92
    # Get user info.
93
    access_token = reponse_token['access_token']
94
    headers = {'Authorization': f'Bearer {access_token}'}
95
    user_response = requests.get(
96
        API_URL_BASE + '/users/@me', headers=headers, timeout=10
97
    )
98
    user_response_data = user_response.json()
99
    if not user_response.ok:
100
        return error()
101
102
    discord_id = user_response_data['id']
103
    discord_username = user_response_data['username']
104
    discord_discriminator = user_response_data['discriminator']
105
106
    # Store Discord id + user-name.
107
    now = datetime.utcnow()
108
    external_id = discord_id
109
    external_name = f'{discord_username}#{discord_discriminator}'
110
    connected_external_accounts_service.connect_external_account(
111
        now,
112
        g.user.id,
113
        SERVICE_NAME,
114
        external_id=external_id,
115
        external_name=external_name,
116
    )
117
118
    flash_success('Discord-Account erfolgreich verbunden.')
119
    return redirect_to('user_settings.view')
120
121
122 1
@blueprint.delete('')
123 1
@login_required
124 1
@respond_no_content
125 1
def remove():
126
    """Unlink Discord account."""
127
    connected_external_account = connected_external_accounts_service.find_connected_external_account_for_user_and_service(
128
        g.user.id, SERVICE_NAME
129
    )
130
    if not connected_external_account:
131
        return
132
133
    disconnect_result = (
134
        connected_external_accounts_service.disconnect_external_account(
135
            connected_external_account.id
136
        )
137
    )
138
    disconnect_result.unwrap()
139