Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
snippets
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
shared
snippets
Commits
503ef1fb
Commit
503ef1fb
authored
Aug 19, 2025
by
Fredrik Wendt
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
The class that spins up an authenticated HTTP client session
parents
Pipeline
#291
failed with stages
Changes
1
Pipelines
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
164 additions
and
0 deletions
+164
-0
auth.py
auth.py
+164
-0
No files found.
auth.py
0 → 100644
View file @
503ef1fb
import
logging
import
sys
import
time
import
uuid
from
hashlib
import
sha256
from
typing
import
Optional
,
Dict
import
requests
from
requests
import
Session
,
TooManyRedirects
from
proagile.authentication
import
CredentialsManager
from
proagile.aws.cloud
import
S3Cache
,
AmazonS3
from
proagile.logging.rollbar_log
import
RollbarLogger
from
proagile.scrumorg.api
import
PRO_AGILE_USER_AGENT
from
proagile.scrumorg.new_parsers.log_error
import
ScrumOrgHtmlError
from
proagile.scrumorg.new_parsers.login_form
import
LoginFormParser
from
proagile.utils
import
TimeUtils
class
SessionCreator
:
"""
Creates an HTTP Request Session that is logged in as the specified username.
"""
_instance
=
None
def
__init__
(
self
):
self
.
log
=
logging
.
getLogger
(
__name__
)
self
.
_sessions
=
dict
()
self
.
_event_logger
=
RollbarLogger
.
instance
()
@
classmethod
def
instance
(
cls
):
if
not
cls
.
_instance
:
cls
.
_instance
=
SessionCreator
()
return
cls
.
_instance
def
acquire_session
(
self
,
username
:
str
):
"""
Always checks that we can access a secret resource, before returning the session.
If the protected resource isn't accessible, login will happen.
"""
cookie_store
=
CookieStore
(
username
)
if
username
in
self
.
_sessions
:
session
=
self
.
_sessions
[
username
]
else
:
session
=
Session
()
session
.
headers
.
update
({
"User-Agent"
:
PRO_AGILE_USER_AGENT
})
self
.
_sessions
[
username
]
=
session
cookies
=
cookie_store
.
fetch
()
self
.
log
.
info
(
f
"Reusing Scrum.org cookies for {username}"
)
if
cookies
:
requests
.
utils
.
add_dict_to_cookiejar
(
session
.
cookies
,
cookies
)
protected_url
=
"https://www.scrum.org/admin/courses/manage"
try
:
response
=
session
.
get
(
protected_url
,
allow_redirects
=
True
)
if
len
(
response
.
history
)
==
0
:
return
session
else
:
html
=
response
.
text
if
"Course Management"
in
html
:
self
.
log
.
info
(
"After being redirected, we ended up on the Course Management page re-using session"
)
return
session
else
:
self
.
_log_response
(
response
)
self
.
log
.
info
(
"After being redirected we didn't end up on Course Management page, new login needed"
)
except
TooManyRedirects
as
e
:
self
.
log
.
info
(
"Received too many redirects: re-using cookies is not working out, getting a fresh session"
)
# remove session with unusable cookies etc
del
self
.
_sessions
[
username
]
session
=
self
.
acquire_unique_clean_session
(
username
)
self
.
_sessions
[
username
]
=
session
cookie_store
.
store
(
dict
(
session
.
cookies
))
self
.
log
.
info
(
f
"Logging in to Scrum.org as {username} completed"
)
return
session
def
acquire_unique_clean_session
(
self
,
username
:
str
):
self
.
log
.
info
(
f
"Logging in on Scrum.org as {username} with a clean session"
)
self
.
_event_logger
.
log_event
(
"acquire_unique_clean_session"
,
distinct_user_id
=
username
)
session
=
Session
()
session
.
headers
.
update
({
"User-Agent"
:
PRO_AGILE_USER_AGENT
})
response
=
session
.
get
(
"https://www.scrum.org/admin/courses/manage"
)
password
=
CredentialsManager
()
.
get_password_for
(
"scrum.org"
,
username
)
data
=
LoginFormParser
(
response
.
text
)
.
extract_hidden_input_fields_with_values
()
data
.
update
({
"loginId"
:
username
,
"password"
:
password
})
try
:
login_result
=
session
.
post
(
"https://accounts.scrum.org/oauth2/authorize"
,
data
=
data
)
if
not
login_result
.
ok
:
self
.
_log_response
(
login_result
)
raise
Exception
(
"Failed to login to Scrum.org - result status was not OK"
)
except
TooManyRedirects
as
e
:
raise
Exception
(
"Failed to login to Scrum.org - too many redirects"
,
e
)
html
=
login_result
.
text
if
"Course Management"
not
in
html
:
ScrumOrgHtmlError
(
html
)
.
log_sdo_error
(
"Login failure"
)
self
.
log
.
debug
(
f
"Full HTML from login attempt: {html}"
)
raise
Exception
(
"Failed to login attempt - couldn't find marker signifying successful login"
)
return
session
def
_log_response
(
self
,
response
):
file_name
=
f
"http/response/{str(uuid.uuid4())}.json"
s3service
=
AmazonS3
(
_s3_region
=
AmazonS3
.
REGION_EU_NORTH_1
)
url_on_s3
=
s3service
.
calculate_public_url_to_s3_file
(
"logs.proagile.se"
,
file_name
)
self
.
log
.
info
(
f
"Logging this HTTP(s) response, recording it in {url_on_s3}"
)
s3service
.
store_public_json_item
(
"logs.proagile.se"
,
file_name
,
{
"cookies"
:
dict
(
response
.
cookies
.
get_dict
()),
"headers"
:
dict
(
response
.
headers
),
"text"
:
response
.
text
,
"url"
:
response
.
url
,
})
class
CookieStore
:
"""
Stores Cookies in an S3 file, as JSON.
"""
def
__init__
(
self
,
user_identity
:
str
):
self
.
user_identity
=
user_identity
self
.
s3_cache
=
S3Cache
()
self
.
_file_name_part
=
None
self
.
_event_logger
=
RollbarLogger
.
instance
()
def
file_name
(
self
)
->
str
:
if
not
self
.
_file_name_part
:
h
=
sha256
()
h
.
update
(
self
.
user_identity
.
encode
(
"utf-8"
))
self
.
_file_name_part
=
h
.
hexdigest
()
return
f
"scrum-org/cookies-{self._file_name_part}.json"
def
store
(
self
,
cookies
:
dict
):
self
.
_event_logger
.
log_event
(
"cookie_stored"
)
self
.
s3_cache
.
store_cache
(
self
.
file_name
(),
{
"cookies"
:
cookies
,
"savedAtSeconds"
:
int
(
time
.
time
()),
"savedAtHuman"
:
TimeUtils
.
now_in_isoformat
()
})
def
fetch
(
self
)
->
Optional
[
Dict
]:
file_name_with_cookie_data
=
self
.
file_name
()
if
not
self
.
s3_cache
.
file_exists
(
file_name_with_cookie_data
):
return
data
=
self
.
s3_cache
.
fetch
(
file_name_with_cookie_data
)
if
"cookies"
not
in
data
:
return
return
data
[
"cookies"
]
if
__name__
==
'__main__'
:
logging
.
basicConfig
(
level
=
logging
.
INFO
,
stream
=
sys
.
stdout
)
x_session
=
SessionCreator
.
instance
()
.
acquire_session
(
"fredrik.wendt@proagile.se"
)
x_response
=
x_session
.
get
(
"https://www.scrum.org/node/12093/edit"
)
print
(
x_response
.
text
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment