Skip to content

Commit 4abeb32

Browse files
committed
Refactor: Remove username from settings.xml, input in password auth flow
Refactors the password authentication flow to remove email storage in settings and instead collect it dynamically. Email is not needed as input for device auth flow. Email is stored in settings as part of auth flow, not as an input for auth flow. - Remove settings from user.login() parameters. - Add new API endpoint get_personal_data() to call GET /sso/v3/me - Remove username from settings.xml, but keep it in settings.py to limit touched files. - Update password auth flow to collect email via keyboard dialog (not hidden) - Update device auth flow to retrieve email from API /sso/v3/me endpoint
1 parent f439ca4 commit 4abeb32

5 files changed

Lines changed: 101 additions & 60 deletions

File tree

plugin.video.arteplussept/resources/lib/api.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
# PATCH empty payload
5151
# needs token in authorization header
5252
'purge_last_viewed': '/sso/v3/lastvieweds/purge',
53+
# GET personal user data (email, firstName, lastName, etc.)
54+
# needs token in authorization header
55+
'personal_data': '/sso/v3/me',
5356
# program_id can be 103520-000-A or LIVE
5457
'player': '/player/v2/config/{lang}/{program_id}',
5558
'program': '/emac/v4/{lang}/web/programs/{program_id}',
@@ -69,8 +72,6 @@
6972
# date=2023-01-17
7073
# 'guide_tv': '/emac/v3/{lang}/{client}/pages/TV_GUIDE/?day={DATE}',
7174
# auth api
72-
'custom_token': '/setCustomToken',
73-
# auth api
7475
'login': '/login',
7576
}
7677
ARTETV_HEADERS = {
@@ -82,13 +83,6 @@
8283
'client': 'tv',
8384
'accept': 'application/json'
8485
}
85-
_API_KEY = '97598990-f0af-427b-893e-9da348d9f5a6'
86-
_COOKIES = {
87-
'TCPID': '123261154911117061452',
88-
# pylint: disable=line-too-long
89-
'TC_PRIVACY': '1%40031%7C29%7C3445%40%40%401677322453596%2C1677322453596%2C1711018453596%40',
90-
'TC_PRIVACY_CENTER': None
91-
}
9286

9387
_ARTETV_ID_URL = 'https://id.arte.tv/auth/realms/myarte-prod/protocol/openid-connect'
9488
DEVICE_AUTH_URL = f"{_ARTETV_ID_URL}/auth/device"
@@ -193,6 +187,19 @@ def purge_last_viewed(tkn):
193187
return reply.status_code
194188

195189

190+
def get_personal_data(tkn):
191+
"""
192+
Retrieve personal user data (email, firstName, lastName, etc.) from Arte API.
193+
Requires authenticated token.
194+
Returns the user data dict from API response or None if request fails.
195+
"""
196+
url = _ARTETV_URL + ARTETV_ENDPOINTS['personal_data']
197+
reply = _load_json_personal_content('artetv_getpersonaldata', url, tkn)
198+
if reply is not None and isinstance(reply.get('data'), list) and len(reply.get('data', [])) > 0:
199+
return reply['data'][0]
200+
return None
201+
202+
196203
def player_video(lang, program_id):
197204
"""Get the info of content program_id from Arte TV API."""
198205
url = _ARTETV_URL + ARTETV_ENDPOINTS['player'].format(lang=lang, program_id=program_id)

plugin.video.arteplussept/resources/lib/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def display_search_page(zone_id, page, query):
242242
@plugin.route('/user/login', name='user_login')
243243
def user_login():
244244
"""Login user with email already set in settings by creating and persisting a token."""
245-
return plugin.finish(succeeded=user.login(plugin, settings))
245+
return plugin.finish(succeeded=user.login(plugin))
246246

247247

248248
@plugin.route('/user/logout', name='user_logout')

plugin.video.arteplussept/resources/lib/settings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ def __init__(self, plugin):
2828
# Arte TV user name
2929
# defaults to empty string to return false with if not str
3030
self.username = plugin.get_setting(
31-
'username') or ""
32-
self.user_mail = plugin.get_setting(
3331
'user_email') or ""
3432
# Enable additional logs managed by plugin: API and display object traces
3533
self.loglevel = plugin.get_setting(

plugin.video.arteplussept/resources/lib/user.py

Lines changed: 84 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
_TTL = 30*24*60
1717

1818

19-
def login(plugin, settings):
19+
def login(plugin):
2020
"""
2121
Unified login entry point.
2222
User chooses between:
23-
- Password login
2423
- Smart TV Device login
24+
- Password login
2525
"""
2626
erase_password_in_old_config(plugin)
2727

@@ -33,70 +33,97 @@ def login(plugin, settings):
3333
]
3434
)
3535
if choice == 0:
36-
return login_with_device_flow(plugin, settings)
36+
return login_with_device_flow(plugin)
3737
if choice == 1:
38-
return login_with_password(plugin, settings)
38+
return login_with_password(plugin)
3939
return False
4040

4141

4242
# ---------------------------------------------------------------------------
4343
# PASSWORD LOGIN
4444
# ---------------------------------------------------------------------------
4545

46-
def login_with_password(plugin, settings):
46+
def login_with_password(plugin):
4747
"""
48-
Get user password from UI, create a token with Arte API, persist it in storage
48+
Get user email and password from UI, create a token with Arte API, persist it in storage
4949
and update settings state to show user is logged in.
5050
"""
51-
# ensure user to log in is identified
52-
usr = settings.username
53-
if not usr:
54-
msg = f"{plugin.addon.getLocalizedString(30020)} : {plugin.addon.getLocalizedString(30021)}"
55-
plugin.notify(msg=msg, image='error')
51+
# get email from user
52+
email = get_user_email(plugin)
53+
if not email:
54+
xbmc.log('Authentication aborted by user - no email entered', level=xbmc.LOGWARNING)
55+
plugin.notify(msg=plugin.addon.getLocalizedString(30020), image='error')
5656
return False
5757

58-
# ensure no user is not logged in
59-
loggedin_usr = settings.user_email
60-
tkn_data = get_cached_token(plugin, usr, True)
61-
if len(loggedin_usr) > 0 and tkn_data:
62-
xbmc.log(
63-
f"\"{loggedin_usr}\" already authenticated : {tkn_data['access_token']}",
64-
level=xbmc.LOGINFO)
65-
# notify user that current token might be replaced
66-
accept_to_replace = xbmcgui.Dialog().yesno(
67-
plugin.addon.getLocalizedString(30015),
68-
plugin.addon.getLocalizedString(30016).format(new_user=usr, old_user=loggedin_usr),
69-
autoclose=10000
70-
)
71-
# user didn't accept replacement, so leave
72-
if not accept_to_replace:
73-
xbmc.log('Authentication aborted by user - keep initial token', level=xbmc.LOGWARNING)
74-
return False
58+
# if user already logged in with this email and token is valid, confirm s/he wants to override
59+
if not want_to_continue_or_override_auth(plugin, email):
60+
return False
7561

7662
# get password
7763
pwd = get_user_password(plugin)
7864
if not pwd:
7965
xbmc.log('Authentication aborted by user - no password entered', level=xbmc.LOGWARNING)
80-
msg = f"{plugin.addon.getLocalizedString(30020)} : {plugin.addon.getLocalizedString(30022)}"
81-
plugin.notify(msg=msg, image='error')
66+
plugin.notify(msg=plugin.addon.getLocalizedString(30020), image='error')
8267
return False
8368

8469
# get token for user and password
85-
tokens = api.authenticate_in_arte(plugin, usr, pwd)
70+
tokens = api.authenticate_in_arte(plugin, email, pwd)
8671
if tokens is None:
8772
xbmc.log('Authentication failed in arte', level=xbmc.LOGERROR)
8873
msg = f"{plugin.addon.getLocalizedString(30020)}"
8974
plugin.notify(msg=msg, image='error')
9075
return False
9176

9277
# store token
93-
set_cached_token(plugin, usr, tokens)
94-
update_settings_state(plugin, usr)
95-
msg = plugin.addon.getLocalizedString(30017).format(user=usr)
78+
set_cached_token(plugin, email, tokens)
79+
set_auth_user_settings(plugin, email)
80+
msg = plugin.addon.getLocalizedString(30017).format(user=email)
9681
plugin.notify(msg=msg, image='info')
9782
return True
9883

9984

85+
def get_user_email(plugin):
86+
"""
87+
Display a keyboard to get user email.
88+
Return None if user didn't enter an email or close the UI.
89+
"""
90+
user_email = ''
91+
keyboard = xbmc.Keyboard(user_email, plugin.addon.getLocalizedString(30019), False)
92+
keyboard.doModal()
93+
if keyboard.isConfirmed() is False:
94+
return None
95+
user_email = keyboard.getText()
96+
if len(user_email) == 0:
97+
return None
98+
return user_email
99+
100+
101+
def want_to_continue_or_override_auth(plugin, new_user):
102+
"""
103+
If user already authenticated (with a valid token and) with same email,
104+
confirm that s/he wants to override her/his token.
105+
Return False if user confirms replacement, True otherwise.
106+
Return true, if token is invalid or emails are different.
107+
"""
108+
# assuming that new user's email and old user's email are the same,
109+
# since token can be retrieved/is indexed by email.
110+
current_tkn = get_cached_token(plugin, new_user, True)
111+
if current_tkn:
112+
xbmc.log(f"\"{new_user}\" already authenticated : {current_tkn['access_token']}")
113+
# notify user that current token might be replaced
114+
accept_to_replace = xbmcgui.Dialog().yesno(
115+
plugin.addon.getLocalizedString(30015),
116+
# old_user=new_user highlight the unsual situation
117+
plugin.addon.getLocalizedString(30016).format(new_user=new_user, old_user=new_user),
118+
autoclose=10000
119+
)
120+
# user didn't accept replacement, so leave
121+
if not accept_to_replace:
122+
xbmc.log('Authentication aborted by user - keep initial token', level=xbmc.LOGWARNING)
123+
return False
124+
return True
125+
126+
100127
def get_user_password(plugin):
101128
"""
102129
Display a keyboard to get user password.
@@ -117,13 +144,14 @@ def get_user_password(plugin):
117144
# SMART TV DEVICE FLOW LOGIN
118145
# ---------------------------------------------------------------------------
119146

120-
def login_with_device_flow(plugin, settings):
147+
def login_with_device_flow(plugin):
121148
"""
122149
Smart TV Device Authorization Flow:
123150
1. Request device_code + user_code
124151
2. Show instructions and code to authenticate on another device
125152
3. Poll token endpoint until success
126-
4. Store token
153+
4. Retrieve email from personal data endpoint
154+
5. Store token
127155
"""
128156
# Step 1 - request device_code
129157
device_info = api.device_authorization_request()
@@ -146,11 +174,23 @@ def login_with_device_flow(plugin, settings):
146174
plugin.notify(plugin.addon.getLocalizedString(30020), image='error')
147175
return False
148176

149-
# Step 4 - store token
150-
usr = settings.username
151-
set_cached_token(plugin, usr, tokens)
152-
update_settings_state(plugin, usr)
153-
msg = plugin.addon.getLocalizedString(30017).format(user=usr)
177+
# Step 4 - retrieve email from personal data endpoint
178+
user_data = api.get_personal_data(tokens)
179+
if user_data is None:
180+
xbmc.log('Failed to retrieve personal data from Arte API', level=xbmc.LOGERROR)
181+
plugin.notify(plugin.addon.getLocalizedString(30020), image='error')
182+
return False
183+
184+
email = user_data.get('email')
185+
if not email:
186+
xbmc.log('Email not found in personal data from Arte API', level=xbmc.LOGERROR)
187+
plugin.notify(plugin.addon.getLocalizedString(30020), image='error')
188+
return False
189+
190+
# Step 5 - store token
191+
set_cached_token(plugin, email, tokens)
192+
set_auth_user_settings(plugin, email)
193+
msg = plugin.addon.getLocalizedString(30017).format(user=email)
154194
plugin.notify(msg=msg, image='info')
155195
return True
156196

@@ -209,7 +249,7 @@ def logout(plugin, settings):
209249
# clear token locally
210250
set_cached_token(plugin, settings.username, '')
211251
clear_cached_tokens(plugin)
212-
update_settings_state(plugin, '')
252+
set_auth_user_settings(plugin, '')
213253
plugin.notify(msg=plugin.addon.getLocalizedString(30018), image='info')
214254
return True
215255

@@ -218,7 +258,7 @@ def logout(plugin, settings):
218258
# ---------------------------------------------------------------------------
219259

220260

221-
def update_settings_state(plugin, email):
261+
def set_auth_user_settings(plugin, email):
222262
"""Update setting state to know who belong the token to"""
223263
message = plugin.addon.getLocalizedString(30017).format(user=email)
224264
if email is None or len(email) <= 0:
@@ -261,4 +301,4 @@ def erase_password_in_old_config(plugin):
261301
to authenticate user.
262302
Deprecated since creation JUL2023, v1.3.0.
263303
"""
264-
return plugin.set_setting('password', '')
304+
return plugin.set_setting('password', '') and plugin.set_setting('username', '')

plugin.video.arteplussept/resources/settings.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@
2929

3030
<!-- Profile -->
3131
<category label="30045">
32-
<setting
33-
id="username"
34-
type="text"
35-
label="30054" />
3632
<setting
3733
id="user_email"
3834
type="text"

0 commit comments

Comments
 (0)