Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
Mycampusscript
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
agse
Mycampusscript
Commits
704d4108
Commit
704d4108
authored
5 months ago
by
prechelt
Browse files
Options
Downloads
Patches
Plain Diff
mycampusscript.py: investigate_jsonlog() reverse-engineering helper function
parent
d9640ae2
Branches
main
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
mycampusscript.py
+78
-3
78 additions, 3 deletions
mycampusscript.py
with
78 additions
and
3 deletions
mycampusscript.py
+
78
−
3
View file @
704d4108
"""
Works roughly as follows:
1. Overall, we script a POST request (RQmain) to a single, complex form to create a Sakai Assignment.
2. However, the form includes one or more uploaded documents and those uploads (must) happen upfront
(RQupload).
3. Worse, the overall form must be sent upfront of even the uploads (RQmain.0)!
At least that is what happens when one opens the upload dialog. A symptom is the fact that one
cannot open the upload dialog unless one has entered a title for the Assignment.
4. This appears to be needed because the URL created for each uploaded document contains an ID
for the assignment.
5. Reverse engineering the details of this is difficult, because GETting the page on which the form resides
(RQmain.get) involves about 40 requests.
6. Therefore, we do not yet know where the identitity of the Assignment freshly created in step 3
is to be found. Our best guess is: Somewhere in the resulting HTML page.
7. Likewise for the URLs of the uploaded documents: They have to be sent as part of the big
(>80 parameters) RQmain request, but the URL is not a main outcome of RQupload.
8. Although the upload dialog is a separate window on top of the RQmain form, RQupload does not
only close that window, it also reloads the RQmain page, presumably to include the freshly uploaded
file in the list of files shown thereon.
"""
import
collections.abc
import
collections.abc
import
datetime
as
dt
import
datetime
as
dt
import
mimetypes
import
mimetypes
...
@@ -101,6 +122,10 @@ ReplacementsFunc = collections.abc.Callable[[StrAnyDict], StrStrDict]
...
@@ -101,6 +122,10 @@ ReplacementsFunc = collections.abc.Callable[[StrAnyDict], StrStrDict]
def
main
(
scriptname
:
str
,
cmdname
:
str
,
configfile
:
str
):
def
main
(
scriptname
:
str
,
cmdname
:
str
,
configfile
:
str
):
investigate_jsonlog
(
"
/c/temp/irgendwas3.json
"
)
return
with
open
(
configfile
,
'
rt
'
)
as
f
:
with
open
(
configfile
,
'
rt
'
)
as
f
:
config
=
yaml
.
load
(
f
,
Loader
=
yaml
.
Loader
)
config
=
yaml
.
load
(
f
,
Loader
=
yaml
.
Loader
)
site_url
:
str
=
find_value_or_help
(
config
,
'
site_url
'
)
site_url
:
str
=
find_value_or_help
(
config
,
'
site_url
'
)
...
@@ -246,8 +271,8 @@ def upload_attachment(config: StrAnyDict, filepath: str):
...
@@ -246,8 +271,8 @@ def upload_attachment(config: StrAnyDict, filepath: str):
See c:/temp/a.json log.entries[164].
See c:/temp/a.json log.entries[164].
"""
"""
base_url
=
f
"
{
config
[
'
site_url
'
]
}
/
{
ASSIGNMENTS_CREATION_ATTACHMENT_UPLOAD_PATH
}
"
base_url
=
f
"
{
config
[
'
site_url
'
]
}
/
{
ASSIGNMENTS_CREATION_ATTACHMENT_UPLOAD_PATH
}
"
query_args
=
dict
(
special
=
'
upload
'
,
panel
=
'
Main
'
,
sakai_action
=
'
doAttachupload
'
,
query_args
=
dict
(
special
=
'
upload
'
,
sakai_action
=
'
doAttachupload
'
,
# panel='Main', ?
csrf_token
=
config
[
CSRF_TOKEN_CONFIGENTRY_NAME
])
sakai_
csrf_token
=
config
[
CSRF_TOKEN_CONFIGENTRY_NAME
])
# TODO 1: continue here:
# TODO 1: continue here:
# We next need to construct the multipart body of the POST request.
# We next need to construct the multipart body of the POST request.
# See "POST Multiple Multipart-Encoded Files" in https://requests.readthedocs.io/en/latest/user/advanced/
# See "POST Multiple Multipart-Encoded Files" in https://requests.readthedocs.io/en/latest/user/advanced/
...
@@ -258,6 +283,7 @@ def upload_attachment(config: StrAnyDict, filepath: str):
...
@@ -258,6 +283,7 @@ def upload_attachment(config: StrAnyDict, filepath: str):
# Das JSON ist dafür unpraktisch, weil der ganze multipart-Rumpf nur als Text dasteht.
# Das JSON ist dafür unpraktisch, weil der ganze multipart-Rumpf nur als Text dasteht.
# In der Word-Datei findet sich die aufgeschlüsselte Form, die der Browser dafür anzeigt.
# In der Word-Datei findet sich die aufgeschlüsselte Form, die der Browser dafür anzeigt.
# Erzeugung eines solchen Requests, siehe https://stackoverflow.com/a/12385661
# Erzeugung eines solchen Requests, siehe https://stackoverflow.com/a/12385661
headers
=
dict
(
Referer
=
f
"
{
config
[
'
site_url
'
]
}
/
{
ASSIGNMENTS_CREATION_ATTACHMENT_UPLOAD_PATH
}
"
)
filename
=
os
.
path
.
basename
(
filepath
)
filename
=
os
.
path
.
basename
(
filepath
)
content_type
,
encoding
=
mimetypes
.
guess_type
(
filename
)
content_type
,
encoding
=
mimetypes
.
guess_type
(
filename
)
multipart_body
=
dict
(
multipart_body
=
dict
(
...
@@ -273,7 +299,54 @@ def upload_attachment(config: StrAnyDict, filepath: str):
...
@@ -273,7 +299,54 @@ def upload_attachment(config: StrAnyDict, filepath: str):
sakai_csrf_token
=
config
[
CSRF_TOKEN_CONFIGENTRY_NAME
]
sakai_csrf_token
=
config
[
CSRF_TOKEN_CONFIGENTRY_NAME
]
)
)
multipart_body
[
'
from
'
]
=
'
list
'
# separate because 'from' is a keyword
multipart_body
[
'
from
'
]
=
'
list
'
# separate because 'from' is a keyword
requests
.
post
(...)
resp
=
requests
.
post
(
base_url
,
params
=
query_args
,
headers
=
headers
,
files
=
multipart_body
,
cookies
=
{
SESSIONCOOKIE_NAME
:
config
[
'
cookies
'
][
SESSIONCOOKIE_NAME
]})
print
(
resp
.
text
)
def
investigate_jsonlog
(
jsonfile
:
str
):
"""
Helper routine for reverse engineering:
We record a complete RQmain-posting episode with two attachments in Firefox
(F12->Network; cogwheel menu->Persist Logs; perform episode; SaveAs:irgendwas.json).
We pull out parts needed for understanding here.
"""
import
json
import
dictns
testdata
=
dictns
.
Namespace
(
dict
(
title
=
"
irgendwas3
"
,
a_id
=
'
dbb411b5-195d-4089-ae66-6e9c8f01b81c
'
,
instructions
=
"
irgendwelche Anleitung
"
,
attachments
=
[
"
ajeunwuk
"
,
"
akeunwuk
"
],
group
=
"
Übung 01
"
,
# name prefix only
))
with
open
(
jsonfile
,
'
r
'
)
as
f
:
log
=
json
.
load
(
f
)
ns
=
dictns
.
Namespace
(
log
)
# the first RQmain POST request is #111, response is 302 to panel=Main
firstrequest
=
ns
.
log
.
entries
[
111
]
assert
firstrequest
.
request
.
postData
.
params
[
5
].
value
==
testdata
.
title
# ns.log.entries[112] is another 302 to ASSIGNMENTS_CREATION_ATTACHMENT_UPLOAD_PATH
# popuprequest = ns.log.entries[113]
url
=
urllib
.
parse
.
urlparse
(
"
http://localhost
"
)
for
i
in
range
(
len
(
ns
.
log
.
entries
)):
# range(111, 178+1):
entry
=
ns
.
log
.
entries
[
i
]
if
"
portal/site
"
not
in
entry
.
request
.
url
:
continue
last_url
=
url
url
=
urllib
.
parse
.
urlparse
(
entry
.
request
.
url
)
if
url
==
last_url
:
msg
=
"
(ditto)
"
elif
url
.
path
==
last_url
.
path
:
msg
=
url
.
query
else
:
msg
=
entry
.
request
.
url
print
(
i
,
entry
.
response
.
status
,
msg
)
# print(popuprequest.response.content.text)
def
site_id
(
site_url
:
str
)
->
str
:
def
site_id
(
site_url
:
str
)
->
str
:
...
@@ -287,6 +360,8 @@ def slurp(filename: str) -> bytes:
...
@@ -287,6 +360,8 @@ def slurp(filename: str) -> bytes:
with
open
(
filename
,
'
rb
'
)
as
f
:
with
open
(
filename
,
'
rb
'
)
as
f
:
return
f
.
read
()
return
f
.
read
()
if
__name__
==
'
__main__
'
:
if
__name__
==
'
__main__
'
:
if
len
(
sys
.
argv
)
!=
2
+
1
or
sys
.
argv
[
1
]
!=
'
create_multigroup_assgmt
'
:
if
len
(
sys
.
argv
)
!=
2
+
1
or
sys
.
argv
[
1
]
!=
'
create_multigroup_assgmt
'
:
print
(
usage
)
print
(
usage
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment