├── .gitignore
├── README.md
└── demo
├── .vscode
└── settings.json
├── auth.py
├── constants.py
├── course.py
├── datasource.py
├── membership.py
├── restdemo.py
├── term.py
├── trusted
└── keytool_crt.pem
└── user.py
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.ipynb
3 |
4 | *.pyc
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BBDN-REST_DEMO-Python
2 | This project contains sample code for demonstrating the Blackboard Learn REST APIs in Python.
3 | This sample code was built with Python 3.9.1.
4 |
5 | ### Project at a glance:
6 | - Target: Blackboard Learn SaaS Release 2015.11.0-ci.23+a9a4758 minimum
7 | - Source Release: v2.0
8 | - Release Date 2020-12-22
9 | - Author: shurrey
10 | - Tested on Blackboard Learn SaaS Release 3900.2.0-rel.34+4ad580a
11 | - Source Release: v1.0
12 | - Release Date 2016-02-24
13 | - Author: moneil
14 | - Tested on Blackboard Learn SaaS Release 2015.11.0-ci.23+a9a4758
15 |
16 | ### Requirements:
17 | - Python 3.9.1
18 | - Developer account - register at https://developer.blackboard.com
19 | - Test instance
20 |
21 |
22 | ### Setting Up Your Development Environment
23 | #### Python development tools
24 | You will first need to install Python 3.9.1. You can use tools like brew or ports to install on Mac, Powershell and the Marketplace on Windows, or run the installation manually.
25 |
26 | You may also install Python tools for your IDE or use a text editor and terminal to run the python code.
27 |
28 |
29 | ### Included Files
30 | restdemo.py - this is the main script
31 | auth.py - this script contains the code for authenticating the application and managing tokens
32 | datasouce.py - this script contains examples for each of the REST endpoints for managing Data Sources in Learn
33 | term.py - this script contains examples for each of the REST endpoints for managing Terms in Learn
34 | course.py - this script contains examples for each of the REST endpoints for managing Courses in Learn
35 | user.py - this script contains examples for each of the REST endpoints for managing Users in Learn
36 | membership.py - this script contains examples for each of the REST endpoints for managing Memberships in Learn Courses
37 |
38 |
39 | ### What it does
40 | The rest demo script demonstrates authenticating a REST application, management and use of the authorization token, and creating, updating, discovering, and deleting supported Learn objects.
41 |
42 | NOTE: Before running the example code you must register a developer account and application as described on the Developer Community Getting Started with REST and Managing REST Integrations in Learn: The REST Integrations Tool for System Administrators pages. You must also configure the script as outlined in the below Configure the Script section.
43 |
44 | When run with only a target URL the script will in the following order
45 | 1. Authenticate
46 | 2. Create, Read, and Update a Data Source
47 | 3. Create, Read, and Update a Term
48 | 4. Create, Read, and Update a Course
49 | 5. Create, Read, and Update a User
50 | 6. Create, Read, and Update a Membership
51 | 7. Delete created objects in reverse order of create - membership, user, course, term, datasource.
52 |
53 | When run with a specific command on an object only that operation will be run - you are responsible for system cleanup.
54 |
55 | All generated output is sent to the terminal (or IDE output window).
56 |
57 | e.g.:
58 | ```
59 | $ python restdemo.py -h
60 | restdemo.py -t|--target -c|--command
61 | e.g.:$ restdemo.py -t www.myschool.edu -c create_course
62 | command: _ where is one of the following:
63 | create, read, update, delete
64 | and <object> is one of the following:
65 | datasource, term, course, user, membership
66 | -t is required; No -c args will run demo in predetermined order.
67 | -c commands require a valid datasource PK1 -
68 | a datasource get will be run in these cases, defaulting to create
69 | if the demo datasource does not exist.
70 | ```
71 |
72 | For example:
73 | ```
74 | $ python restdemo.py -t localhost:9877
75 | ```
76 | Runs the full CRUD demo
77 |
78 | ```
79 | $ python restdemo.py -t localhost:9877 -c create_datasource
80 | ```
81 | Runs the datasource.py:createDataSource() demo code and does not clean up the remote system after running you must run:
82 |
83 | ```
84 | $ python restdemo.py -t localhost:9877 -c delete_datasource
85 | ```
86 | to remove the created data source.
87 |
88 |
89 | ## Running the Demo!
90 | ### Setup Your Test Server
91 | To run the demo if you have not already done so you must as outlined above register the application via the Developer Portal and add the application to your test environment using the REST API Integration tool.
92 |
93 |
94 | ### Configuring the Script
95 | Before executing the script to run against your test server you must configure it with your registered application's Key and Secret.
96 |
97 | Open auth.py and edit lines 37 and 38.
98 | On line 37 replace "insert_your_application_key_here" with the key issued when you registered your application.
99 | On line 38 replace "insert_your_application_secret_here" with the secret issued when you registered your application.
100 |
101 | Once you have setup your test server and changed auth.py to reflect your application's key and secret you may run the command line tools as outlined above or via your IDE.
102 |
103 |
104 | ### Conclusion
105 | For a thorough walkthrough of this code, visit the corresponding Blackboard Developer Community documentation REST Demo Using Python .
106 |
--------------------------------------------------------------------------------
/demo/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | }
--------------------------------------------------------------------------------
/demo/auth.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | """
12 | PYTHON and Self-signed certificates:
13 | To enable code to recognize the DVM self signed certificate you have to provide Python
14 | with the public Key
15 |
16 | 1. Start the DVM and ssh into it...
17 | $ vagrant up
18 | $ vagrant ssh
19 | 2. Copy the keystore password from the config file
20 | $ cd /usr/local/blackboard/config
21 | $ more /usr/local/blackboard/config/bb-config.properties
22 | find the keystore password and copy it - you will use this in step 3 for
23 | exporting the pem file
24 | 3. Export the cert and keys
25 | $ cd /usr/local/blackboard/config/keystores
26 | $ keytool -exportcert -alias tomcat -keystore tomcat.keystore -rfc -file /vagrant/keytool_crt.pem
27 | 4. Make sure you can read the file:
28 | $ openssl x509 -in /vagrant/keytool_crt.pem -inform pem -noout -text
29 | 4. copy the .pem file to a location your project can access it.
30 | 5. update config.py to point to your .pem file
31 | 6. add dev.bbdn.local to your hosts file and save:
32 | 127.0.0.1 localhost dev.bbdn.local
33 | 7. test access to to your DVM using dev.bbdn.local, if it doesn't work restart and retry
34 |
35 | now for all requests against your DVM the pem will be used to verify the server
36 | NOTE: that for production code you should change 'verify=CERTPATH' to 'verify=False' if
37 | not using the server derived .pem file, or remove 'verify=...' completely if using
38 | commercial certificates.
39 | see http://docs.python-requests.org/en/master/user/advanced/ for more information on
40 | SSL sessions in Python.
41 | """
42 |
43 | import json
44 | import requests
45 | from requests.adapters import HTTPAdapter
46 | from requests.packages.urllib3.poolmanager import PoolManager
47 | import datetime
48 | import time
49 | #from datetime import now
50 | import ssl
51 | import sys
52 | from constants import *
53 |
54 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
55 | # certificates e.g.: Learn Dev VM
56 | # May be removed if you migrated the cert as outlined in auth.py
57 | class Tls1Adapter(HTTPAdapter):
58 | def init_poolmanager(self, connections, maxsize, block=False):
59 | self.poolmanager = PoolManager(num_pools=connections,
60 | maxsize=maxsize,
61 | block=block,
62 | ssl_version=ssl.PROTOCOL_TLSv1)
63 |
64 | class AuthToken():
65 | target_url = ''
66 |
67 | def __init__(self, URL):
68 |
69 | self.KEY = "insert_your_application_key_here"
70 | self.SECRET = "insert_your_application_secret_here"
71 |
72 | #self.SECRET = "biExBJNI1IXiBBXpV8g01JJJjmXKHSg7" #Example Only. Change to your secret
73 | #self.KEY = "9cb9384a-3662-410d-9953-fe73cc374b81"#Example Only. Change to your key
74 |
75 | self.CREDENTIALS = 'client_credentials'
76 | self.PAYLOAD = {
77 | 'grant_type':'client_credentials'
78 | }
79 | self.TOKEN = None
80 | self.target_url = URL
81 | self.EXPIRES_AT = ''
82 | self.cert_path='./trusted/keytool_crt.pem'
83 |
84 | def getKey(self):
85 | return self.KEY
86 |
87 | def getSecret(self):
88 | return self.SECRET
89 |
90 | def setToken(self):
91 | oauth_path = '/learn/api/public/v1/oauth2/token'
92 | OAUTH_URL = 'https://' + self.target_url + oauth_path
93 |
94 | if self.TOKEN is None:
95 | session = requests.session()
96 | #session.mount('https://', Tls1Adapter()) # remove for production
97 |
98 | # Authenticate
99 | print("[auth:setToken] POST Request URL: " + OAUTH_URL)
100 | print("[auth:setToken] JSON Payload: \n" + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
101 | #r = requests.post(url, data=data, verify='/path/to/public_key.pem')
102 | r = session.post(OAUTH_URL, data=self.PAYLOAD, auth=(self.KEY, self.SECRET), verify=False)
103 | # r = session.post(OAUTH_URL, data=self.PAYLOAD, auth=(self.KEY, self.SECRET), verify=CERTPATH)
104 |
105 | print("[auth:setToken()] STATUS CODE: " + str(r.status_code) )
106 | #strip quotes from result for better dumps
107 | res = json.loads(r.text)
108 | print("[auth:setToken()] RESPONSE: \n" + json.dumps(res,indent=4, separators=(',', ': ')))
109 |
110 | if r.status_code == 200:
111 | parsed_json = json.loads(r.text)
112 | self.TOKEN = parsed_json['access_token']
113 | self.EXPIRES = parsed_json['expires_in']
114 | m, s = divmod(self.EXPIRES, 60)
115 | #h, m = divmod(m, 60)
116 | #print "%d:%02d:%02d" % (h, m, s)
117 | self.NOW = datetime.datetime.now()
118 | self.EXPIRES_AT = self.NOW + datetime.timedelta(seconds = s, minutes = m)
119 | print ("[auth:setToken()] Token Expires at " + self.EXPIRES_AT.strftime("%H:%M:%S"))
120 |
121 | print ("[auth:setToken()] TOKEN: " + self.TOKEN)
122 |
123 | #there is the possibility the reaquired token may expire
124 | #before we are done so perform expiration sanity check...
125 | if self.isExpired(self.EXPIRES_AT):
126 | self.setToken()
127 |
128 | else:
129 | print("[auth:setToken()] ERROR")
130 | else:
131 | print ("[auth:setToken()] TOKEN set")
132 |
133 | def getToken(self):
134 | #if token time is less than a one second then
135 | # print that we are pausing to clear
136 | # re-auth and return the new token
137 | if self.isExpired(self.EXPIRES_AT):
138 | self.setToken()
139 |
140 | return self.TOKEN
141 |
142 | def revokeToken(self):
143 | revoke_path = '/learn/api/public/v1/oauth2/revoke'
144 | revoke_URL = 'https://' + self.target_url + revoke_path
145 |
146 | print("[auth:revokeToken()] KEY: " + self.KEY)
147 | print("[auth:revokeToken()] SECRET: " + self.SECRET)
148 | print("[auth:revokeToken()] TOKEN: " + self.TOKEN)
149 | print("[auth:revokeToken()] revoke_URL: " + revoke_URL)
150 | self.PAYLOAD = {
151 | 'token':self.TOKEN
152 | }
153 |
154 | if self.TOKEN != '':
155 | print("[auth:revokeToken()] TOKEN not empty...able to revoke")
156 | print("[auth:revokeToken()] POST PAYLOAD: ")
157 | for keys,values in self.PAYLOAD.items():
158 | print("\t\t\t" + keys + ":" + values)
159 | session = requests.session()
160 | session.mount('https://', Tls1Adapter()) # remove for production
161 |
162 | # revoke token
163 | print("[auth:revokeToken] Request URL: " + revoke_URL)
164 | print("[auth:revokeToken] JSON Payload: \n " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
165 | r = session.post(revoke_URL, data=self.PAYLOAD, auth=(self.KEY, self.SECRET), verify=CERTPATH)
166 |
167 | print("[auth:revokeToken()] STATUS CODE: " + str(r.status_code) )
168 | print("[auth:revokeToken()] RESPONSE: " + r.text)
169 |
170 | if r.status_code == 200:
171 | print("[auth:revokeToken()] Token Revoked")
172 | else:
173 | print("[auth] ERROR on token revoke")
174 | else:
175 | print ("[auth:revokeToken()] Must have set a token to revoke a token...")
176 |
177 |
178 | def isExpired(self, expiration_datetime):
179 | expired = False
180 | print ("[auth:isExpired()] Token Expires at " + expiration_datetime.strftime("%H:%M:%S"))
181 |
182 | time_left = (expiration_datetime - datetime.datetime.now()).total_seconds()
183 | print ("[auth:isExpired()] Time Left on Token (in seconds): " + str(time_left))
184 | if time_left < 1:
185 | print ("[auth:isExpired()] Token almost expired retrieving new token in two seconds.")
186 | time.sleep( 1 )
187 | expired = True
188 |
189 | return expired
190 |
--------------------------------------------------------------------------------
/demo/constants.py:
--------------------------------------------------------------------------------
1 | # constants file for BBDN-REST_DEMO-Python
2 |
3 | #IDs for create
4 | DSKEXTERNALID = 'BBDN-PYTHON-REST-DEMO-DSK'
5 | COURSEEXTERNALID = 'BBDN-PYTHON-REST-DEMO-COURSE'
6 | TERMEXTERNALID = 'BBDN-PYTHON-REST-DEMO-TERM'
7 | USEREXTERNALID = 'BBDN-PYTHON-REST-DEMO-USER'
8 | PAGINATIONLIMIT = 5
9 | COURSEGETFIELDS = 'externalId,courseId,name,availability,id'
10 | USERGETFIELDS = 'externalId,name'
11 | TERMSGETFIELDS = 'externalId,name'
12 | DSKSGETFIELDS = 'externalId,name'
13 | CRSMEMBERSHIPGETFIELDS = "externalId,availability"
14 | USRMEMBERSHIPGETFIELDS = "externalId,availability"
15 | CERTPATH = "./trusted/keytool_crt.pem"
16 |
17 |
--------------------------------------------------------------------------------
/demo/course.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | import json
12 | import html
13 | import requests
14 | from requests.adapters import HTTPAdapter
15 | from requests.packages.urllib3.poolmanager import PoolManager
16 | import ssl
17 | import sys
18 | from constants import *
19 |
20 | requests.packages.urllib3.disable_warnings()
21 |
22 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
23 | # certificates e.g.: Learn Dev VM
24 | # May be removed if you migrated the cert as outlined in auth.py
25 | class Tls1Adapter(HTTPAdapter):
26 | def init_poolmanager(self, connections, maxsize, block=False):
27 | self.poolmanager = PoolManager(num_pools=connections,
28 | maxsize=maxsize,
29 | block=block,
30 | ssl_version=ssl.PROTOCOL_TLSv1)
31 |
32 | class Course():
33 |
34 | def __init__(self, target_url, token):
35 | self.target_url = target_url
36 | self.token = token
37 | self.courses_Path = '/learn/api/public/v1/courses' #create(POST)/get(GET)
38 | self.course_Path = '/learn/api/public/v1/courses/externalId:'
39 | self.courses_Path_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, COURSEGETFIELDS)
40 | self.termId = None
41 |
42 |
43 |
44 |
45 | def execute(self, command, dsk, token):
46 | if "create" in command:
47 | print('[Course:execute] : ' + command)
48 | self.createCourse(dsk, token)
49 | elif "read_all" in command:
50 | print('[Course:execute] : ' + command)
51 | self.getCourses(token)
52 | elif "read" in command:
53 | print('[Course:execute] : ' + command)
54 | self.getCourse(token)
55 | elif "update" in command:
56 | print('[Course:execute] : ' + command)
57 | self.updateCourse(dsk, token)
58 | elif "delete" in command:
59 | print('[Course:execute] : ' + command)
60 | self.deleteCourse(token)
61 |
62 |
63 | def getCourses(self, token):
64 | #demo limits returned page count to constants.PAGINATIONLIMIT and
65 | #limits result data fields to constants.COURSEGETFIELDS
66 | print('[Course:getCourses()] token: ' + token)
67 | #"Authorization: Bearer $token"
68 | authStr = 'Bearer ' + token
69 | print('[Course:getCourses()] authStr: ' + authStr)
70 | #session = requests.session()
71 | #session.mount('https://', Tls1Adapter()) # remove for production
72 |
73 | nextPage = True
74 | nextpageURL = None
75 | while nextPage:
76 | print ("[Course:getCourses()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
77 | print ("[Course:getCourses()] NEXTPAGE: %s" % nextPage)
78 | print ("[Course:getCourses()] NEXTPAGEURL: %s" % nextpageURL)
79 | if nextpageURL:
80 | print ("[Course:getCourses()] NEXTPAGE: %s, so update URL parameters." % nextPage)
81 | self.courses_Path_Params = nextpageURL.replace(self.courses_Path, '')
82 | print ("[Course:getCourses()] UPDATED URL PARAMS: %s" %self.courses_Path_Params)
83 | print("[Course:getCourses()] GET Request URL: https://" + self.target_url + self.courses_Path + self.courses_Path_Params)
84 | print("[Course:getCourses()] JSON Payload: NONE REQUIRED")
85 | r = requests.get("https://" + self.target_url + self.courses_Path + self.courses_Path_Params, headers={'Authorization':authStr}, verify=False)
86 |
87 | print("[Course:getCourses()] STATUS CODE: " + str(r.status_code) )
88 | print("[Course:getCourses()] RESPONSE:")
89 | if r.text:
90 | res = json.loads(r.text)
91 | print(json.dumps(res,indent=4, separators=(',', ': ')))
92 | try:
93 | nextpageURL = res['paging']['nextPage']
94 | nextPage=True
95 | #continue to process records here before retrieving more
96 | except KeyError as err:
97 | nextPage=False
98 | nextpageURL=None
99 | print ("[Course:getCourses()] No (more) records.")
100 | else:
101 | print("NONE")
102 |
103 |
104 |
105 | def createCourse(self, dsk, token):
106 | #"Authorization: Bearer $token"
107 | authStr = 'Bearer ' + token
108 |
109 | self.PAYLOAD = {
110 | "externalId": COURSEEXTERNALID,
111 | "dataSourceId": "externalId:%s" % DSKEXTERNALID,
112 | "courseId": COURSEEXTERNALID,
113 | "name":"Course used for REST demo",
114 | "description":"Course used for REST demo",
115 | "allowGuests":"true",
116 | "readOnly": "false",
117 | "availability": {
118 | "duration":"continuous"
119 | }
120 | }
121 |
122 | #session = requests.session()
123 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
124 | print("[Course:createCourse()] POST Request URL: https://" + self.target_url + self.courses_Path)
125 | print("[Courses:createCourse()] JSON Payload: \n " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
126 | r = requests.post("https://" + self.target_url + self.courses_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
127 | print("[Course:createCourse()] STATUS CODE: " + str(r.status_code) )
128 | print("[Course:createCourse()] RESPONSE:")
129 | if r.text:
130 | res = json.loads(r.text)
131 | print(json.dumps(res,indent=4, separators=(',', ': ')))
132 | else:
133 | print("NONE")
134 |
135 | def getCourse(self, token):
136 | print('[Course:getCourse()] token: ' + token)
137 | #"Authorization: Bearer $token"
138 | authStr = 'Bearer ' + token
139 | print('[Course:getCourses] authStr: ' + authStr)
140 | #session = requests.session()
141 | #session.mount('https://', Tls1Adapter()) # remove for production
142 | print("[Course:getCourse()] GET Request URL: https://" + self.target_url + self.course_Path + COURSEEXTERNALID)
143 |
144 | r = requests.get("https://" + self.target_url + self.course_Path+COURSEEXTERNALID, headers={'Authorization':authStr}, verify=False)
145 |
146 | print("[Course:getCourse()] STATUS CODE: " + str(r.status_code) )
147 | print("[Course:getCourse()] RESPONSE:")
148 | if r.text:
149 | res = json.loads(r.text)
150 | print(json.dumps(res,indent=4, separators=(',', ': ')))
151 | else:
152 | print("NONE")
153 | done = False
154 |
155 | def updateCourse(self, dsk, token):
156 | #"Authorization: Bearer $token"
157 | authStr = 'Bearer ' + token
158 | print("[Course:updateCourse()] COURSEEXTERNALID: " + COURSEEXTERNALID)
159 |
160 | self.PAYLOAD = {
161 | "externalId":COURSEEXTERNALID,
162 | "dataSourceId": "externalId:%s" % DSKEXTERNALID,
163 | "courseId":COURSEEXTERNALID,
164 | "name":"Course used for REST Python demo",
165 | "description":"Course used for REST Python demo",
166 | "allowGuests":"true",
167 | "readOnly": "false",
168 | #"termId":constants.TERMEXTERNALID,
169 | "availability": {
170 | "available":"Yes",
171 | "duration":"continuous"
172 | }
173 | }
174 | #session = requests.session()
175 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
176 | print("[Course:updateCourse()] PATCH Request URL: https://" + self.target_url + self.course_Path + COURSEEXTERNALID)
177 | print("[Courses:updateCourse()] Result: \n " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
178 | r = requests.patch("https://" + self.target_url + self.course_Path+COURSEEXTERNALID, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
179 |
180 | print("[Course:updateCourse()] STATUS CODE: " + str(r.status_code) )
181 | print("[Course:updateCourse()] RESPONSE:")
182 | if r.text:
183 | res = json.loads(r.text)
184 | print(json.dumps(res,indent=4, separators=(',', ': ')))
185 | else:
186 | print("NONE")
187 |
188 | def deleteCourse(self, token):
189 | #"Authorization: Bearer $token"
190 | authStr = 'Bearer ' + token
191 | print("[Course:deleteCourse()] COURSEEXTERNALID: " + COURSEEXTERNALID)
192 |
193 | #session = requests.session()
194 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
195 | print("[Course:deleteCourse()] DELETE Request URL: https://" + self.target_url + self.course_Path + COURSEEXTERNALID)
196 | print("[Courses:deleteCourse()] JSON Payload: NONE REQUIRED")
197 | r = requests.delete("https://" + self.target_url + self.course_Path+COURSEEXTERNALID, headers={'Authorization':authStr}, verify=False)
198 |
199 | print("[Course:deleteCourse()] STATUS CODE: " + str(r.status_code) )
200 | print("[Course:deleteCourse()] RESPONSE:")
201 | if r.text:
202 | res = json.loads(r.text)
203 | print(json.dumps(res,indent=4, separators=(',', ': ')))
204 | else:
205 | print("NONE")
206 |
--------------------------------------------------------------------------------
/demo/datasource.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | from constants import *
12 | import json
13 | import requests
14 | from requests.adapters import HTTPAdapter
15 | from requests.packages.urllib3.poolmanager import PoolManager
16 | import ssl
17 | import sys
18 | from constants import *
19 |
20 |
21 | requests.packages.urllib3.disable_warnings()
22 |
23 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
24 | # certificates e.g.: Learn Dev VM
25 | # May be removed if you migrated the cert as outlined in auth.py
26 | class Tls1Adapter(HTTPAdapter):
27 | def init_poolmanager(self, connections, maxsize, block=False):
28 | self.poolmanager = PoolManager(num_pools=connections,
29 | maxsize=maxsize,
30 | block=block,
31 | ssl_version=ssl.PROTOCOL_TLSv1)
32 |
33 | class DataSource():
34 |
35 |
36 | def __init__(self, target_url, token):
37 | self.target_url = target_url
38 | self.token = token
39 | self.datasource_PK1 = None
40 | self.DATASOURCES_PATH = '/learn/api/public/v1/dataSources' #create(POST)/get(GET)
41 | self.DATASOURCE_PATH = '/learn/api/public/v1/dataSources/externalId:'
42 | self.DATASOURCES_PATH_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, DSKSGETFIELDS)
43 |
44 |
45 | def execute(self, command, token):
46 |
47 | if "create" in command:
48 | print('[DataSource:execute] : ' + command)
49 | self.createDataSource(token)
50 | elif "read_all" in command:
51 | print('[DataSource:execute] : ' + command)
52 | self.getDataSources(token)
53 | elif "read" in command:
54 | print('[DataSource:execute] : ' + command)
55 | self.getDataSource(token)
56 | elif "update" in command:
57 | print('[DataSource:execute] : ' + command)
58 | self.updateDataSource(token)
59 | elif "delete" in command:
60 | print('[DataSource:execute] : ' + command)
61 | self.deleteDataSource(token)
62 |
63 |
64 | def getDataSources(self, token):
65 | print('[DataSource:getDataSources] token: ' + token)
66 | #"Authorization: Bearer $token"
67 | authStr = 'Bearer ' + token
68 | print('[DataSource:getDataSources] authStr: ' + authStr)
69 | #session = requests.session()
70 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
71 |
72 | nextPage = True
73 | nextpageURL = None
74 | while nextPage:
75 | print ("[DataSource:getDataSources()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
76 | print ("[DataSource:getDataSources()] NEXTPAGE: %s" % nextPage)
77 | print ("[DataSource:getDataSources()] NEXTPAGEURL: %s" % nextpageURL)
78 | if nextpageURL:
79 | print ("[DataSource:getDataSources()] NEXTPAGE: %s, so update URL parameters." % nextPage)
80 | self.DATASOURCES_PATH_Params = nextpageURL.replace(self.DATASOURCES_PATH_Params, '')
81 | print ("[DataSource:getDataSources()] UPDATED URL PARAMS: %s" %self.DATASOURCES_PATH_Params)
82 | print("[DataSource:getDataSources()] GET Request URL: https://" + self.target_url + self.DATASOURCES_PATH + self.DATASOURCES_PATH_Params)
83 | print("[DataSource:getDataSources()] JSON Payload: NONE REQUIRED")
84 | r = requests.get("https://" + self.target_url + self.DATASOURCES_PATH + self.DATASOURCES_PATH_Params, headers={'Authorization':authStr}, verify=False)
85 |
86 | print("[DataSource:getDataSources()] STATUS CODE: " + str(r.status_code) )
87 | print("[DataSource:getDataSources()] RESPONSE:")
88 | if r.text:
89 | res = json.loads(r.text)
90 | print(json.dumps(res,indent=4, separators=(',', ': ')))
91 | try:
92 | nextpageURL = res['paging']['nextPage']
93 | nextPage=True
94 | #continue to process records here before retrieving more
95 | except KeyError as err:
96 | nextPage=False
97 | nextpageURL=None
98 | print ("[DataSource:getDataSources()] No (more) records.")
99 | else:
100 | print("NONE")
101 |
102 |
103 | def createDataSource(self, token):
104 | #"Authorization: Bearer $token"
105 | authStr = "Bearer " + token
106 | self.PAYLOAD = {"externalId":"%s" % DSKEXTERNALID, "description":"Data Source used for REST demo"}
107 |
108 | #session = requests.session()
109 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
110 |
111 | print("[DataSource:createDataSource()] POST Request URL: https://" + self.target_url + self.DATASOURCES_PATH)
112 | print("[DataSource:createDataSource()] JSON Payload: \n" + json.dumps(self.PAYLOAD,indent=4, separators=(',', ': ')))
113 |
114 | try:
115 | print("1")
116 | r = requests.post("https://" + self.target_url + self.DATASOURCES_PATH, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
117 | print("[DataSource:createDataSource()] STATUS CODE: " + str(r.status_code) )
118 | print("[DataSource:createDataSource()] RESPONSE:")
119 | if r.text:
120 | print("2")
121 | res = json.loads(r.text)
122 | print(json.dumps(res,indent=4, separators=(',', ': ')))
123 | parsed_json = json.loads(r.text)
124 | self.datasource_PK1 = parsed_json['externalId']
125 | print ("[DataSource:createDataSource()] datasource_PK1:" + self.datasource_PK1)
126 | print ("[DataSource:createDataSource()] datasource_externalId:" + parsed_json['externalId'])
127 | else:
128 | print("NONE")
129 |
130 | print("3")
131 |
132 | if r.status_code == 429:
133 | print("[datasource:getDataSource] Error 429 Too Many Requests. Exiting.")
134 | sys.exit(2)
135 | except requests.RequestException as re:
136 | print("[datasource:createDataSource()] Error cannot connect to requested server. Exiting.\n" + str(re))
137 | sys.exit(2)
138 |
139 | def getDataSource(self, token):
140 | #print('[DataSource:getDataSource()] token: ' + token)
141 | #"Authorization: Bearer $token"
142 | authStr = 'Bearer ' + token
143 | print('[DataSource:getDataSource()] authStr: ' + authStr)
144 | #session = requests.session()
145 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
146 |
147 | print("[DataSource:getDataSource()] GET Request URL: https://" + self.target_url + self.DATASOURCE_PATH+DSKEXTERNALID)
148 | print("[DataSource:getDataSource()] JSON Payload: NONE REQUIRED")
149 | r = requests.get("https://" + self.target_url + self.DATASOURCE_PATH+DSKEXTERNALID, headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
150 |
151 | print("[DataSource:getDataSource()] STATUS CODE: " + str(r.status_code) )
152 | print("[DataSource:getDataSource()] RESPONSE:")
153 | if r.text:
154 | res = json.loads(r.text)
155 | print(json.dumps(res,indent=4, separators=(',', ': ')))
156 | else:
157 | print("NONE")
158 |
159 | if r.status_code == 200:
160 | parsed_json = json.loads(r.text)
161 | self.datasource_PK1 = parsed_json['id']
162 | print ("[DataSource:getDataSource()] datasource_PK1:" + self.datasource_PK1)
163 |
164 |
165 |
166 | def updateDataSource(self, token):
167 | #"Authorization: Bearer $token"
168 | authStr = 'Bearer ' + token
169 | print("[DataSource:updateDataSource()] DSKEXTERNALID: " + DSKEXTERNALID)
170 |
171 | self.PAYLOAD = {"description":"Demo Data Source used for REST Python Demo"}
172 |
173 | #session = requests.session()
174 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
175 |
176 | print("[DataSource:updateDataSource()] PATCH Request URL: https://" + self.target_url + self.DATASOURCE_PATH + DSKEXTERNALID)
177 | print("[DataSource:updateDataSource()] JSON Payload: \n" + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
178 | r = requests.patch("https://" + self.target_url + self.DATASOURCE_PATH+DSKEXTERNALID, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
179 |
180 | print("[DataSource:updateDataSource()] STATUS CODE: " + str(r.status_code) )
181 | print("[DataSource:updateDataSource()] RESPONSE:")
182 | if r.text:
183 | res = json.loads(r.text)
184 | print(json.dumps(res,indent=4, separators=(',', ': ')))
185 | else:
186 | print("NONE")
187 |
188 |
189 | def deleteDataSource(self, token):
190 | #"Authorization: Bearer $token"
191 | authStr = 'Bearer ' + token
192 | print("[DataSource:deleteDataSource()] DSKEXTERNALID: " + DSKEXTERNALID)
193 |
194 | #session = requests.session()
195 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
196 |
197 | print("[DataSource:deleteDataSource()] DELETE Request URL: https://" + self.target_url + self.DATASOURCE_PATH + DSKEXTERNALID)
198 | print("[DataSource:deleteDataSource()] JSON Payload: NONE REQUIRED")
199 | r = requests.delete("https://" + self.target_url + self.DATASOURCE_PATH+DSKEXTERNALID, headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
200 |
201 | print("[DataSource:deleteDataSource()] STATUS CODE: " + str(r.status_code) )
202 | print("[DataSource:deleteDataSource()] RESPONSE:")
203 | if r.text:
204 | res = json.loads(r.text)
205 | print(json.dumps(res,indent=4, separators=(',', ': ')))
206 | else:
207 | print("NONE")
208 |
209 | def checkDataSource(self, token):
210 | self.getDataSource(token)
211 | if not self.datasource_PK1:
212 | print("[datasource::checkDataSource] Data Source %s does not exist, creating." % DSKEXTERNALID)
213 | self.createDataSource(token)
214 |
--------------------------------------------------------------------------------
/demo/membership.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 |
8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 |
10 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11 |
12 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 | """
14 |
15 | import json
16 | import requests
17 | from requests.adapters import HTTPAdapter
18 | from requests.packages.urllib3.poolmanager import PoolManager
19 | import ssl
20 | import sys
21 | from constants import *
22 |
23 | requests.packages.urllib3.disable_warnings()
24 |
25 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
26 | # certificates e.g.: Learn Dev VM
27 | # May be removed if you migrated the cert as outlined in auth.py
28 | class Tls1Adapter(HTTPAdapter):
29 | def init_poolmanager(self, connections, maxsize, block=False):
30 | self.poolmanager = PoolManager(num_pools=connections,
31 | maxsize=maxsize,
32 | block=block,
33 | ssl_version=ssl.PROTOCOL_TLSv1)
34 |
35 | class Membership():
36 |
37 | def __init__(self, target_url, token):
38 | self.target_url = target_url
39 | self.token = token
40 | self.memberships_Path = '/learn/api/public/v1/courses/courseId/users' #create(POST)/get(GET)
41 | self.membership_Path = '/learn/api/public/v1/courses/courseId/users/userId'
42 | self.userMembships_Path = '/learn/api/public/v1/users/userId/courses'
43 | self.usermemberships_Path_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, USRMEMBERSHIPGETFIELDS)
44 | self.coursememberships_Path_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, CRSMEMBERSHIPGETFIELDS)
45 |
46 |
47 | def execute(self, command, dsk, token):
48 | if "create" in command:
49 | print('[Membership:execute] : ' + command)
50 | self.createMembership(dsk, token)
51 | elif "read_all_user_memberships" in command:
52 | print ('[Membership:execute] : ' + command)
53 | self.getUserMemberships(token)
54 | elif "read_all_course_memberships" in command:
55 | print('[Membership:execute] : ' + command)
56 | self.getCourseMemberships(token)
57 | elif "read" in command:
58 | print('[Membership:execute] : ' + command)
59 | self.getMembership(token)
60 | elif "update" in command:
61 | print('[Membership:execute] : ' + command)
62 | self.updateMembership(dsk, token)
63 | elif "delete" in command:
64 | print('[Membership:execute] : ' + command)
65 | self.deleteMembership(token)
66 |
67 |
68 | def getCourseMemberships(self, token):
69 | #GET /learn/api/public/v1/courses/{courseId}/users
70 | #prints all the memberships for a specific course
71 |
72 | print('[Membership:getCourseMemberships] token: ' + token)
73 | #"Authorization: Bearer $token"
74 | authStr = 'Bearer ' + token
75 | print('[Membership:getCourseMemberships] authStr: ' + authStr)
76 | #session = requests.session()
77 | #session.mount('https://', Tls1Adapter()) # remove for production
78 |
79 | replacement = "externalId:"+COURSEEXTERNALID
80 | memberships_Path = self.memberships_Path
81 | memberships_Path = memberships_Path.replace("courseId", replacement)
82 |
83 |
84 | nextPage = True
85 | nextpageURL = None
86 | while nextPage:
87 | print ("[Membership:getCourseMemberships()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
88 | print ("[Membership:getCourseMemberships()] NEXTPAGE: %s" % nextPage)
89 | print ("[Membership:getCourseMemberships()] NEXTPAGEURL: %s" % nextpageURL)
90 | if nextpageURL:
91 | print ("[Membership:getCourseMemberships()] NEXTPAGE: %s, so update URL parameters." % nextPage)
92 | self.coursememberships_Path_Params = nextpageURL.replace(self.memberships_Path, '')
93 | print ("[Membership:getCourseMemberships()] UPDATED URL PARAMS: %s" %self.coursememberships_Path_Params)
94 | print("[Membership:getCourseMemberships()] GET Request URL: https://" + self.target_url + self.memberships_Path + self.coursememberships_Path_Params)
95 | print("[Membership:getCourseMemberships()] JSON Payload: NONE REQUIRED")
96 | #r = session.get("https://" + self.target_url + self.memberships_Path + self.memberships_Path_Params, headers={'Authorization':authStr}, verify=False)
97 | r = requests.get("https://" + self.target_url + self.memberships_Path + self.coursememberships_Path_Params, headers={'Authorization':authStr}, verify=False)
98 |
99 | print("[Membership:getCourseMemberships()] STATUS CODE: " + str(r.status_code) )
100 | print("[Membership:getCourseMemberships()] RESPONSE:")
101 | if r.text:
102 | res = json.loads(r.text)
103 | print(json.dumps(res,indent=4, separators=(',', ': ')))
104 | try:
105 | nextpageURL = res['paging']['nextPage']
106 | nextPage=True
107 | #continue to process records here before retrieving more
108 | except KeyError as err:
109 | nextPage=False
110 | nextpageURL=None
111 | print ("[Membership:getCourseMemberships()] No (more) records.")
112 | else:
113 | print("NONE")
114 |
115 |
116 |
117 | # print("[Membership:getMemberships()] GET Request URL: https://" + self.target_url + memberships_Path)
118 | # print("[Membership:getMemberships()] JSON Payload: NONE REQUIRED")
119 | # r = session.get("https://" + self.target_url + memberships_Path, headers={'Authorization':authStr}, verify=False)
120 | # print("[Membership:getMemberships()] STATUS CODE: " + str(r.status_code) )
121 | # print("[Membership:getMemberships()] RESPONSE:")
122 | # if r.text:
123 | # res = json.loads(r.text)
124 | # print(json.dumps(res,indent=4, separators=(',', ': ')))
125 | # else:
126 | # print("NONE")
127 |
128 | def getUserMemberships(self, token):
129 | #GET /learn/api/public/v1/users/{userId}/courses
130 | #prints all the memberships for a specific user
131 |
132 | print('[Membership:getCourseMemberships] token: ' + token)
133 | #"Authorization: Bearer $token"
134 | authStr = 'Bearer ' + token
135 | print('[Membership:getCourseMemberships] authStr: ' + authStr)
136 | #session = requests.session()
137 | #session.mount('https://', Tls1Adapter()) # remove for production
138 |
139 | replacement = "externalId:"+USEREXTERNALID
140 | userMembships_Path = self.userMembships_Path
141 | userMembships_Path = userMembships_Path.replace("userId", replacement)
142 |
143 |
144 |
145 | print ("Membership:getUsersMemberships() not yet implemented.")
146 | nextPage = True
147 | nextpageURL = None
148 | while nextPage:
149 | print ("[Membership:getUserMemberships()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
150 | print ("[Membership:getUserMemberships()] NEXTPAGE: %s" % nextPage)
151 | print ("[Membership:getUserMemberships()] NEXTPAGEURL: %s" % nextpageURL)
152 | if nextpageURL:
153 | print ("[Membership:getUserMemberships()] NEXTPAGE: %s, so update URL parameters." % nextPage)
154 | self.courses_Path_Params = nextpageURL.replace(self.memberships_Path, '')
155 | print ("[Membership:getUserMemberships()] UPDATED URL PARAMS: %s" %self.usermemberships_Path_Params)
156 | print("[Membership:getUserMemberships()] GET Request URL: https://" + self.target_url + self.memberships_Path + self.usermemberships_Path_Params)
157 | print("[Membership:getUserMemberships()] JSON Payload: NONE REQUIRED")
158 | #r = session.get("https://" + self.target_url + self.memberships_Path + self.memberships_Path_Params, headers={'Authorization':authStr}, verify=False)
159 | r = requests.get("https://" + self.target_url + self.memberships_Path + self.usermemberships_Path_Params, headers={'Authorization':authStr}, verify=False)
160 |
161 | print("[Membership:getUserMemberships()] STATUS CODE: " + str(r.status_code) )
162 | print("[Membership:getUserMemberships()] RESPONSE:")
163 | if r.text:
164 | res = json.loads(r.text)
165 | print(json.dumps(res,indent=4, separators=(',', ': ')))
166 | try:
167 | nextpageURL = res['paging']['nextPage']
168 | nextPage=True
169 | #continue to process records here before retrieving more
170 | except KeyError as err:
171 | nextPage=False
172 | nextpageURL=None
173 | print ("[Membership:getUserMemberships()] No (more) records.")
174 | else:
175 | print("NONE")
176 |
177 | def createMembership(self, dsk, token):
178 | #"Authorization: Bearer $token"
179 | authStr = 'Bearer ' + token
180 |
181 | self.PAYLOAD = {
182 | "dataSourceId":"externalId:%s" % DSKEXTERNALID,
183 | "availability": {
184 | "available":"Yes"
185 | },
186 | "courseRoleId":"Instructor"
187 | }
188 |
189 | #session = requests.session()
190 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
191 |
192 | #self.membership_Path = '/learn/api/public/v1/courses/courseId/users/userId'
193 | replacement = "externalId:"+COURSEEXTERNALID
194 | membership_Path = self.membership_Path
195 | membership_Path = membership_Path.replace("courseId", replacement)
196 |
197 | replacement = "externalId:" + USEREXTERNALID
198 | membership_Path = membership_Path.replace("userId", replacement)
199 |
200 | print("[Membership:getMemberships()] PUT Request URL: https://" + self.target_url + membership_Path)
201 | print("[Membership:getMemberships()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
202 | #r = session.put("https://" + self.target_url + membership_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
203 | r = requests.put("https://" + self.target_url + membership_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
204 | print("[Membership:getMemberships()] STATUS CODE: " + str(r.status_code) )
205 | print("[Membership:getMemberships()] RESPONSE:")
206 | if r.text:
207 | res = json.loads(r.text)
208 | print(json.dumps(res,indent=4, separators=(',', ': ')))
209 | else:
210 | print("NONE")
211 |
212 | def getMembership(self, token):
213 | #GET /learn/api/public/v1/courses/{courseId}/users/{userId}
214 | print('[Membership:getMemberships] token: ' + token)
215 | #"Authorization: Bearer $token"
216 | authStr = 'Bearer ' + token
217 | print('[Membership:getMemberships] authStr: ' + authStr)
218 | #session = requests.session()
219 | #session.mount('https://', Tls1Adapter()) # remove for production
220 |
221 | replacement = "externalId:"+COURSEEXTERNALID
222 | membership_Path = self.membership_Path
223 | membership_Path = membership_Path.replace("courseId", replacement)
224 |
225 | replacement = "externalId:" + USEREXTERNALID
226 | membership_Path = membership_Path.replace("userId", replacement)
227 |
228 | print("[Membership:getMemberships()] GET Request URL: https://" + self.target_url + membership_Path)
229 | print("[Membership:getMemberships()] JSON Payload: NONE REQUIRED")
230 | r = requests.get("https://" + self.target_url + membership_Path, headers={'Authorization':authStr}, verify=False)
231 | print("[Membership:getMembership()] STATUS CODE: " + str(r.status_code) )
232 | print("[Membership:getMembership()] RESPONSE:")
233 | if r.text:
234 | res = json.loads(r.text)
235 | print(json.dumps(res,indent=4, separators=(',', ': ')))
236 | else:
237 | print("NONE")
238 |
239 | def readUserMemberships(self, token):
240 | #GET /learn/api/public/v1/users/{userId}/courses
241 | print('[Membership:readUserMemberships] token: ' + token)
242 | #"Authorization: Bearer $token"
243 | authStr = 'Bearer ' + token
244 | print('[Membership:readUserMemberships] authStr: ' + authStr)
245 | #session = requests.session()
246 | #session.mount('https://', Tls1Adapter()) # remove for production
247 |
248 | replacement = "externalId:" + USEREXTERNALID
249 | userMemberships_Path = self.userMembships_Path
250 | userMemberships_Path = userMemberships_Path.replace("userId", replacement)
251 | print("[Membership:readUserMemberships()] GET Request URL: https://" + self.target_url + userMemberships_Path)
252 | print("[Membership:readUserMemberships()] JSON Payload: NONE REQUIRED")
253 | r = requests.get("https://" + self.target_url + userMemberships_Path, headers={'Authorization':authStr}, verify=False)
254 | print("[Membership:readUserMemberships()] STATUS CODE: " + str(r.status_code) )
255 | print("[Membership:readUserMemberships()] RESPONSE:")
256 | if r.text:
257 | res = json.loads(r.text)
258 | print(json.dumps(res,indent=4, separators=(',', ': ')))
259 | else:
260 | print("NONE")
261 |
262 |
263 | def updateMembership(self, dsk, token):
264 | #"Authorization: Bearer $token"
265 | authStr = 'Bearer ' + token
266 |
267 | self.PAYLOAD = {
268 | "dataSourceId":"externalId:%s" % DSKEXTERNALID,
269 | "availability": {
270 | "available":"No"
271 | },
272 | "courseRoleId":"Student"
273 | }
274 |
275 | #session = requests.session()
276 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
277 |
278 | replacement = "externalId:"+ COURSEEXTERNALID
279 | membership_Path = self.membership_Path
280 | membership_Path = membership_Path.replace("courseId", replacement)
281 |
282 | replacement = "externalId:" + USEREXTERNALID
283 | membership_Path = membership_Path.replace("userId", replacement)
284 |
285 | print("[Membership:updateMembership()] Request URL: https://" + self.target_url + membership_Path)
286 | print("[Membership:updateMembership()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
287 | #r = session.patch("https://" + self.target_url + membership_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
288 | r = requests.patch("https://" + self.target_url + membership_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
289 | print("[Membership:updateMembership()] STATUS CODE: " + str(r.status_code) )
290 | print("[Membership:updateMembership()] RESPONSE:")
291 | if r.text:
292 | res = json.loads(r.text)
293 | print(json.dumps(res,indent=4, separators=(',', ': ')))
294 | else:
295 | print("NONE")
296 |
297 | def deleteMembership(self, token):
298 | #"Authorization: Bearer $token"
299 | authStr = 'Bearer ' + token
300 |
301 | #session = requests.session()
302 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
303 |
304 | replacement = "externalId:"+ COURSEEXTERNALID
305 | membership_Path = self.membership_Path
306 | membership_Path = membership_Path.replace("courseId", replacement)
307 |
308 | replacement = "externalId:" + USEREXTERNALID
309 | membership_Path = membership_Path.replace("userId", replacement)
310 |
311 | print("[Membership:deleteMembership()] DELETE Request URL: https://" + self.target_url + membership_Path)
312 | print("[Membership:deleteMembership()] JSON Payload: NONE REQUIRED")
313 | #r = session.delete("https://" + self.target_url + membership_Path, headers={'Authorization':authStr}, verify=False)
314 | r = requests.delete("https://" + self.target_url + membership_Path, headers={'Authorization':authStr}, verify=False)
315 | print("[Membership:deleteMembership()] STATUS CODE: " + str(r.status_code) )
316 | print("[Membership:deleteMembership()] RESPONSE:")
317 | if r.text:
318 | res = json.loads(r.text)
319 | print(json.dumps(res,indent=4, separators=(',', ': ')))
320 | else:
321 | print("NONE")
322 |
--------------------------------------------------------------------------------
/demo/restdemo.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | from auth import AuthToken
12 | from datasource import DataSource
13 | from term import Term
14 | from course import Course
15 | from user import User
16 | from membership import Membership
17 | from constants import *
18 |
19 | import sys
20 | import getopt
21 |
22 |
23 |
24 | def main(argv):
25 | target_url = ''
26 | COMMAND = ''
27 | ALL = False
28 | AUTH = False
29 | DATASOURCE = False
30 | TERM = False
31 | COURSE = False
32 | USER = False
33 | MEMBERSHIP = False
34 | CLEANUP = False
35 |
36 | datasource_PK1 = None
37 | datasource_session = None
38 |
39 | usageStr = "\nrestdemo.py -t|--target -c|--command \n"
40 | usageStr += "e.g restdemo.py -t www.myschool.edu -c create_course\n"
41 | usageStr += "command: _ where is one of the following:\n"
42 | usageStr += "\tcreate, read, read_all_, read_all_course_memberships, read_all_user_memberships, update, delete\n"
43 | usageStr += "and is one of the following:\n"
44 | usageStr += "\tdatasource, term, course, user\n"
45 | usageStr += "-t is required; No -c args will run demo in predetermined order.\n"
46 | usageStr += "'-c authorize' demomonstrates the authorization process and does not create objects."
47 | usageStr += "-c commands require a valid datasource PK1 - \n"
48 | usageStr += "\ta datasource get will be run in these cases, defaulting to create\n"
49 | usageStr += "\tif the demo datasource does not exist."
50 |
51 | if len(sys.argv) > 1: #there are command line arguments
52 | try:
53 | opts, args = getopt.getopt(argv,"ht:c:",["target=","command="])
54 | except getopt.GetoptError:
55 | print (usageStr)
56 | sys.exit(2)
57 | for opt, arg in opts:
58 | if opt == '-h':
59 | print (usageStr)
60 | sys.exit()
61 | elif opt == '-d':
62 | print ("Deleting at end of run.")
63 | CLEANUP = True
64 | elif opt in ("-t", "--target"):
65 | target_url = arg.lstrip()
66 | elif opt in ("-c", "--command"):
67 | COMMAND = arg
68 | else:
69 | COMMAND = "Run All"
70 | print ('[main] Target is:', target_url)
71 | print ('[main] Command is:', COMMAND)
72 |
73 |
74 | else:
75 | print(usageStr)
76 | sys.exit(2)
77 |
78 |
79 | #Set up some booleans for processing flags and order of processing
80 | if "course" in COMMAND:
81 | print("[main] Run course command")
82 | COURSE = True
83 | elif "membership" in COMMAND:
84 | print("[main] Run membership command")
85 | MEMBERSHIP = True
86 | elif "user" in COMMAND:
87 | print("[main] Run user command")
88 | USER = True
89 | elif "term" in COMMAND:
90 | print("[main] Run term command")
91 | TERM = True
92 | elif "datasource" in COMMAND:
93 | print("[main] Run datasource command")
94 | DATASOURCE = True
95 | elif "authorize" in COMMAND:
96 | print("[main] Run authorization command")
97 | AUTH = True
98 | else:
99 | print("[main] Empty Command: Run All\n")
100 | ALL = True
101 |
102 | print ('\n[main] Acquiring auth token...\n')
103 | authorized_session = AuthToken(target_url)
104 | authorized_session.setToken()
105 | print ('\n[main] Returned token: ' + authorized_session.getToken() + '\n')
106 |
107 | if not AUTH:
108 | #run commands in required order if running ALL
109 | if DATASOURCE or ALL:
110 | #process Datasource command
111 | print("\n[main] Run datasource command: " + ('ALL' if ALL else COMMAND) + '...')
112 | datasource_session = DataSource(target_url, authorized_session.getToken())
113 | if 'datasource' in COMMAND:
114 | datasource_session.execute(COMMAND, authorized_session.getToken())
115 | else:
116 | if not datasource_PK1 or datasource_PK1 is None:
117 | datasource_session.createDataSource(authorized_session.getToken())
118 | datasource_session.getDataSource(authorized_session.getToken())
119 | datasource_session.getDataSources(authorized_session.getToken())
120 | datasource_session.updateDataSource(authorized_session.getToken())
121 |
122 | if TERM or ALL:
123 | term_session = Term(target_url, authorized_session.getToken())
124 | #process term command
125 | print("\n[main] Run term command: " + ('ALL' if ALL else COMMAND) + '...')
126 | if 'term' in COMMAND:
127 | if (('delete' in COMMAND) or ('read' in COMMAND)):
128 | print ("[main] Deleting or getting does not require a datasource.")
129 | else:
130 | if not datasource_PK1:
131 | print("[main] confirm datasource.")
132 | datasource_session = DataSource(target_url, authorized_session.getToken())
133 | datasource_session.checkDataSource(authorized_session.getToken())
134 |
135 | term_session.execute(COMMAND, "externalId:"+TERMEXTERNALID, authorized_session.getToken())
136 | else:
137 | term_session.getTerms(authorized_session.getToken())
138 | term_session.createTerm("externalId:"+DSKEXTERNALID, authorized_session.getToken())
139 | term_session.getTerm(authorized_session.getToken())
140 | term_session.updateTerm("externalId:"+DSKEXTERNALID, authorized_session.getToken())
141 |
142 | if COURSE or ALL:
143 | course_session = Course(target_url, authorized_session.getToken())
144 | #process course command
145 | print("\n[main] Run course command: " + ('ALL' if ALL else COMMAND) + '...')
146 | if 'course' in COMMAND:
147 | if (('delete' in COMMAND) or ('read' in COMMAND)):
148 | print ("[main] Deleting or getting does not require a datasource.")
149 | else:
150 | if not datasource_PK1:
151 | print("[main] confirm datasource.")
152 | datasource_session = DataSource(target_url, authorized_session.getToken())
153 | datasource_session.checkDataSource(authorized_session.getToken())
154 |
155 | course_session.execute(COMMAND, "externalId:"+DSKEXTERNALID, authorized_session.getToken())
156 | else:
157 | course_session.getCourses(authorized_session.getToken())
158 | course_session.createCourse("externalId:"+DSKEXTERNALID, authorized_session.getToken())
159 | course_session.getCourse(authorized_session.getToken())
160 | course_session.updateCourse("externalId:"+DSKEXTERNALID, authorized_session.getToken())
161 |
162 | if USER or ALL:
163 | user_session = User(target_url, authorized_session.getToken())
164 | #process user command
165 | print("\n[main] Run user command: " + ('ALL' if ALL else COMMAND) + '...')
166 | if 'user' in COMMAND:
167 | if (('delete' in COMMAND) or ('read' in COMMAND)):
168 | print ("[main] Deleting or getting does not require a datasource.")
169 | else:
170 | if not datasource_PK1:
171 | print("[main] confirm datasource.")
172 | datasource_session = DataSource(target_url, authorized_session.getToken())
173 | datasource_session.checkDataSource(authorized_session.getToken())
174 |
175 | user_session.execute(COMMAND, "externalId:"+DSKEXTERNALID, authorized_session.getToken())
176 | else:
177 | user_session.getUsers(authorized_session.getToken())
178 | user_session.createUser("externalId:"+DSKEXTERNALID, authorized_session.getToken())
179 | user_session.getUser(authorized_session.getToken())
180 | user_session.updateUser("externalId:"+DSKEXTERNALID, authorized_session.getToken())
181 |
182 | if MEMBERSHIP or ALL:
183 | membership_session = Membership(target_url, authorized_session.getToken())
184 |
185 | #process membership command
186 | print("\n[main] Run membership command: " + ('ALL' if ALL else COMMAND) + '...')
187 | if 'membership' in COMMAND:
188 | if (('delete' in COMMAND) or ('read' in COMMAND)):
189 | print ("[main] Deleting or getting does not require a datasource.")
190 | else:
191 | if not datasource_PK1:
192 | print("[main] confirm datasource.")
193 | datasource_session = DataSource(target_url, authorized_session.getToken())
194 | datasource_session.checkDataSource(authorized_session.getToken())
195 |
196 | membership_session.execute(COMMAND, "externalId:"+DSKEXTERNALID, authorized_session.getToken())
197 | else:
198 | membership_session.getCourseMemberships(authorized_session.getToken())
199 | membership_session.createMembership("externalId:"+DSKEXTERNALID, authorized_session.getToken())
200 | membership_session.getUserMemberships(authorized_session.getToken())
201 | membership_session.updateMembership("externalId:"+DSKEXTERNALID, authorized_session.getToken())
202 | membership_session.readUserMemberships(authorized_session.getToken())
203 | #clean up if not using individual commands
204 | if ALL:
205 | print('\n[main] Completing Demo and deleting created objects...')
206 | print ("[main] Deleting membership")
207 | membership_session.deleteMembership(authorized_session.getToken())
208 | print ("[main] Deleting Course")
209 | user_session.deleteUser(authorized_session.getToken())
210 | print ("[main] Deleting Course")
211 | course_session.deleteCourse(authorized_session.getToken())
212 | print ("[main] Deleting Term")
213 | term_session.deleteTerm(authorized_session.getToken())
214 | print ("[main] Deleting DataSource")
215 | datasource_session.deleteDataSource(authorized_session.getToken())
216 | else:
217 | print("\nRemember to delete created demo objects!")
218 |
219 |
220 | print("[main] Processing Complete")
221 |
222 | #revoke issued Token
223 | #authorized_session.revokeToken()
224 |
225 | if __name__ == '__main__':
226 | main(sys.argv[1:])
227 |
--------------------------------------------------------------------------------
/demo/term.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | import json
12 | import requests
13 | from requests.adapters import HTTPAdapter
14 | from requests.packages.urllib3.poolmanager import PoolManager
15 | import ssl
16 | import sys
17 | from constants import *
18 |
19 | requests.packages.urllib3.disable_warnings()
20 |
21 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
22 | # certificates e.g.: Learn Dev VM
23 | # May be removed if you migrated the cert as outlined in auth.py
24 | class Tls1Adapter(HTTPAdapter):
25 | def init_poolmanager(self, connections, maxsize, block=False):
26 | self.poolmanager = PoolManager(num_pools=connections,
27 | maxsize=maxsize,
28 | block=block,
29 | ssl_version=ssl.PROTOCOL_TLSv1)
30 |
31 | class Term():
32 |
33 | def __init__(self, target_url, token):
34 | self.target_url = target_url
35 | self.token = token
36 | self.terms_Path = '/learn/api/public/v1/terms' #create(POST)/get(GET)
37 | self.term_Path = '/learn/api/public/v1/terms/externalId:'
38 | self.terms_Path_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, TERMSGETFIELDS)
39 |
40 |
41 |
42 | def execute(self, command, dsk, token):
43 | if "create" in command:
44 | print('[Term:execute] : ' + command)
45 | self.createTerm(dsk, token)
46 | elif "read_all" in command:
47 | print('[Term:execute] : ' + command)
48 | self.getTerms(token)
49 | elif "read" in command:
50 | print('[Term:execute] : ' + command)
51 | self.getTerm(token)
52 | elif "update" in command:
53 | print('[Term:execute] : ' + command)
54 | self.updateTerm(dsk, token)
55 | elif "delete" in command:
56 | print('[Term:execute] : ' + command)
57 | self.deleteTerm(token)
58 |
59 |
60 | def getTerms(self, token):
61 | print('[Term:getTerms] token: ' + token)
62 | #"Authorization: Bearer $token"
63 | authStr = 'Bearer ' + token
64 | print('[Term:getTerms] authStr: ' + authStr)
65 | #session = requests.session()
66 | #session.mount('https://', Tls1Adapter()) # remove for production
67 |
68 | nextPage = True
69 | nextpageURL = None
70 | while nextPage:
71 | print ("[Term:getTerms()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
72 | print ("[Term:getTerms()] NEXTPAGE: %s" % nextPage)
73 | print ("[Term:getTerms()] NEXTPAGEURL: %s" % nextpageURL)
74 | if nextpageURL:
75 | print ("[Term:getTerms()] NEXTPAGE: %s, so update URL parameters." % nextPage)
76 | self.terms_Path_Params = nextpageURL.replace(self.terms_Path, '')
77 | print ("[Term:getTerms()] UPDATED URL PARAMS: %s" %self.terms_Path_Params)
78 | print("[Term:getTerms()] GET Request URL: https://" + self.target_url + self.terms_Path + self.terms_Path_Params)
79 | print("[Term:getTerms()] JSON Payload: NONE REQUIRED")
80 | r = requests.get("https://" + self.target_url + self.terms_Path + self.terms_Path_Params, headers={'Authorization':authStr}, verify=False)
81 |
82 | print("[Term:getTerms()] STATUS CODE: " + str(r.status_code) )
83 | print("[Term:getTerms()] RESPONSE:")
84 | if r.text:
85 | res = json.loads(r.text)
86 | print(json.dumps(res,indent=4, separators=(',', ': ')))
87 | try:
88 | nextpageURL = res['paging']['nextPage']
89 | nextPage=True
90 | #continue to process records here before retrieving more
91 | except KeyError as err:
92 | nextPage=False
93 | nextpageURL=None
94 | print ("[Term:getTerms()] No (more) records.")
95 | else:
96 | print("NONE")
97 |
98 |
99 |
100 |
101 | def createTerm(self, dsk, token):
102 | #"Authorization: Bearer $token"
103 | authStr = 'Bearer ' + token
104 | self.PAYLOAD = {
105 | "externalId":TERMEXTERNALID,
106 | "dataSourceId": "externalId:%s" % DSKEXTERNALID,
107 | "name":"REST Demo Term",
108 | "description": "Term used for REST demo",
109 | "availability": {
110 | "duration":"Continuous"
111 | }
112 | }
113 |
114 | #session = requests.session()
115 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
116 |
117 | print("[Term:createTerm()] POST Request URL: https://" + self.target_url + self.terms_Path)
118 | print("[Term:createTerm()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
119 | r = requests.post("https://" + self.target_url + self.terms_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
120 |
121 | print("[Term:createTerm()] STATUS CODE: " + str(r.status_code) )
122 | print("[Term:createTerm()] RESPONSE:")
123 | if r.text:
124 | res = json.loads(r.text)
125 | print(json.dumps(res,indent=4, separators=(',', ': ')))
126 | else:
127 | print("NONE")
128 |
129 | def getTerm(self, token):
130 | print('[Term:getTerm()] token: ' + token)
131 | #"Authorization: Bearer $token"
132 | authStr = 'Bearer ' + token
133 | print('[Term:getTerm()] authStr: ' + authStr)
134 | #session = requests.session()
135 | #session.mount('https://', Tls1Adapter()) # remove for production
136 |
137 | print("[Term:getTerm()] GET Request URL: https://" + self.target_url + self.term_Path+TERMEXTERNALID)
138 | print("[Term:getTerm()] JSON Payload: NONE REQUIRED")
139 | r = requests.get("https://" + self.target_url + self.term_Path+TERMEXTERNALID, headers={'Authorization':authStr}, verify=False)
140 |
141 | print("[Term:getTerm()] STATUS CODE: " + str(r.status_code) )
142 | print("[Term:getTerm()] RESPONSE:")
143 | if r.text:
144 | res = json.loads(r.text)
145 | print(json.dumps(res,indent=4, separators=(',', ': ')))
146 | else:
147 | print("NONE")
148 |
149 | def updateTerm(self, dsk, token):
150 | #"Authorization: Bearer $token"
151 | authStr = 'Bearer ' + token
152 | print("[Term:updateTerm()] Term ExternalId: " + TERMEXTERNALID)
153 |
154 | self.PAYLOAD = {
155 | "externalId":TERMEXTERNALID,
156 | "dataSourceId": "externalId:%s" % DSKEXTERNALID,
157 | "name":"REST Python Demo Term",
158 | "description": "Term used for REST Python demo",
159 | "availability": {
160 | "duration":"continuous"
161 | }
162 | }
163 | #session = requests.session()
164 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
165 |
166 | print("[Term:updateTerm()] PATCH Request URL: https://" + self.target_url + self.term_Path+TERMEXTERNALID)
167 | print("[Term:updateTerm()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
168 | r = requests.patch("https://" + self.target_url + self.term_Path+TERMEXTERNALID, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
169 |
170 | print("[Term:updateTerm()] STATUS CODE: " + str(r.status_code) )
171 | print("[Term:updateTerm()] RESPONSE:")
172 | if r.text:
173 | res = json.loads(r.text)
174 | print(json.dumps(res,indent=4, separators=(',', ': ')))
175 | else:
176 | print("NONE")
177 |
178 | def deleteTerm(self, token):
179 | #"Authorization: Bearer $token"
180 | authStr = 'Bearer ' + token
181 | print("[Term:deleteTerm()] Term ExternalId: " + TERMEXTERNALID)
182 |
183 | #session = requests.session()
184 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
185 |
186 | print("[Term:getTerms()] DELETE Request URL: https://" + self.target_url + self.term_Path+TERMEXTERNALID)
187 | print("[Term:getTerms()] JSON Payload: NONE REQUIRED")
188 | #r = session.delete("https://" + self.target_url + self.term_Path+self.termExternalId, headers={'Authorization':authStr}, verify=False)
189 | r = requests.delete("https://" + self.target_url+self.term_Path+TERMEXTERNALID, headers={'Authorization':authStr}, verify=False)
190 | print("[Term:deleteTerm()] STATUS CODE: " + str(r.status_code) )
191 | print("[Term:deleteTerm()] RESPONSE:")
192 | if r.text:
193 | res = json.loads(r.text)
194 | print(json.dumps(res,indent=4, separators=(',', ': ')))
195 | else:
196 | print("NONE")
197 |
--------------------------------------------------------------------------------
/demo/trusted/keytool_crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICsjCCAZqgAwIBAgIIZhffvonH6uowDQYJKoZIhvcNAQELBQAwGTEXMBUGA1UEAxMOZGV2LmJi
3 | ZG4ubG9jYWwwHhcNMTYwNDE1MDAwMDAwWhcNMTgwNDE1MDAwMDAwWjAZMRcwFQYDVQQDEw5kZXYu
4 | YmJkbi5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMFwUcfsC77Pyyn21Y9S
5 | rZde5zvBY8CA8yUKc/jJj87BXM4gBZLB5lM7ypX8pLjgHuLIAquqf0a5bn1xkxRaK/Cf2v9bEM4m
6 | PHy6ZI5iLq5A90Q0JOGAiQsscBRiijSm8KYFAvpThmpsq7/K2mwS9fQtfyX1mRzrdVKDavQlrqm/
7 | I7kg6EJAfzSEGTEl0NknyU0M22deiWtNbGwOcibMg9+yM+I863fU8FR0sSGm6RQwF0g/NcZpC94v
8 | hWTFdVdweRq+76PA73bbsaQukam1/+RxbG3r99FFFtN4HAt26Ak5/yCtt+Nn85263a1PEV/D4bCU
9 | 3sbh+OOo+hRxQ29l/xsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAH563dPcVzxbGF1gE1PGQdlR+
10 | Pzuux0EutLT0TE17y9yp5mgffweNJ3lgpcjwrhHrR0FeBbQvz9Pp62QsQgnJF2kHgwqq1MPqsPqe
11 | Mo78BIzTRZ45EtzE/p0oyoLBR1S9EWAyUtJBA8gKfGQiboj9qN5ZsX/KYny27b7ukMRJ8q/mB2XF
12 | c3LO6+T/ubTd3ZHN1nNfnqceecewx7CCwXMC5PblWq1bZ/rU0+Exv7+h7Ff7fkjx+wfmaR3lGf6L
13 | y+ceMVnS+r/VHYeah9kxb4qAPMIDqkmJLPLNXGl7Gc5Ph466y+bATFu6ywnqj6EJK0InxFXN1nvg
14 | gHafgVXjIMG0+g==
15 | -----END CERTIFICATE-----
16 |
--------------------------------------------------------------------------------
/demo/user.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (C) 2016, Blackboard Inc.
3 | All rights reserved.
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
7 | Neither the name of Blackboard Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
8 | THIS SOFTWARE IS PROVIDED BY BLACKBOARD INC ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLACKBOARD INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 | """
10 |
11 | import json
12 | import requests
13 | from requests.adapters import HTTPAdapter
14 | from requests.packages.urllib3.poolmanager import PoolManager
15 | import ssl
16 | import sys
17 | from constants import *
18 |
19 | requests.packages.urllib3.disable_warnings()
20 |
21 | #Tls1Adapter allows for connection to sites with non-CA/self-signed
22 | # certificates e.g.: Learn Dev VM
23 | # May be removed if you migrated the cert as outlined in auth.py
24 | class Tls1Adapter(HTTPAdapter):
25 | def init_poolmanager(self, connections, maxsize, block=False):
26 | self.poolmanager = PoolManager(num_pools=connections,
27 | maxsize=maxsize,
28 | block=block,
29 | ssl_version=ssl.PROTOCOL_TLSv1)
30 |
31 | class User():
32 |
33 | def __init__(self, target_url, token):
34 | self.target_url = target_url
35 | self.token = token
36 | self.users_Path = '/learn/api/public/v1/users' #create(POST)/get(GET)
37 | self.user_Path = '/learn/api/public/v1/users/externalId:'
38 | self.users_Path_Params = "?limit=%s&fields=%s" % (PAGINATIONLIMIT, USERGETFIELDS)
39 |
40 |
41 |
42 |
43 | def execute(self, command, dsk, token):
44 | if "create" in command:
45 | print('[User:execute] : ' + command)
46 | self.createUser(dsk, token)
47 | elif "read_all" in command:
48 | print('[User:execute] : ' + command)
49 | self.getUsers(token)
50 | elif "read" in command:
51 | print('[User:execute] : ' + command)
52 | self.getUser(token)
53 | elif "update" in command:
54 | print('[User:execute] : ' + command)
55 | self.updateUser(dsk, token)
56 | elif "delete" in command:
57 | print('[User:execute] : ' + command)
58 | self.deleteUser(token)
59 |
60 |
61 | def getUsers(self, token):
62 | #demo limits returned page count to constants.PAGINATIONLIMIT and
63 | #limits result data fields to constants.USERGETFIELDS
64 | print('[User:getUsers] token: ' + token)
65 | #"Authorization: Bearer $token"
66 | authStr = 'Bearer ' + token
67 | print('[User:getUsers] authStr: ' + authStr)
68 | #session = requests.session()
69 | #session.mount('https://', Tls1Adapter()) # remove for production
70 |
71 | nextPage = True
72 | nextpageURL = None
73 | while nextPage:
74 | print ("[User:getUsers()] ENTERING WHILE LOOP FOR NEXT PAGE CHECK")
75 | print ("[User:getUsers()] NEXTPAGE: %s" % nextPage)
76 | print ("[User:getUsers()] NEXTPAGEURL: %s" % nextpageURL)
77 | if nextpageURL:
78 | print ("[User:getUsers()] NEXTPAGE: %s, so update URL parameters." % nextPage)
79 | self.users_Path_Params = nextpageURL.replace(self.users_Path, '')
80 | print ("[User:getUsers()] UPDATED URL PARAMS: %s" %self.users_Path_Params)
81 | print("[User:getUsers()] GET Request URL: https://" + self.target_url + self.users_Path + self.users_Path_Params)
82 | print("[User:getUsers()] JSON Payload: NONE REQUIRED")
83 | r = requests.get("https://" + self.target_url + self.users_Path + self.users_Path_Params, headers={'Authorization':authStr}, verify=False)
84 |
85 | print("[User:getUsers()] STATUS CODE: " + str(r.status_code) )
86 | print("[User:getUsers()] RESPONSE:")
87 | if r.text:
88 | res = json.loads(r.text)
89 | print(json.dumps(res,indent=4, separators=(',', ': ')))
90 | try:
91 | nextpageURL = res['paging']['nextPage']
92 | nextPage=True
93 | except KeyError as err:
94 | nextPage=False
95 | nextpageURL=None
96 | print ("[User:getUsers()] No (more) records.")
97 | else:
98 | print("NONE")
99 |
100 | def createUser(self, dsk, token):
101 | #"Authorization: Bearer $token"
102 | authStr = 'Bearer ' + token
103 |
104 | self.PAYLOAD = {
105 | "externalId":USEREXTERNALID,
106 | "dataSourceId": dsk, #self.dskExternalId, Supported soon.
107 | "userName":"python_demo",
108 | "password":"python61",
109 | "availability": {
110 | "available": "Yes"
111 | },
112 | "name": {
113 | "given": "Python",
114 | "family": "Demo",
115 | },
116 | "contact": {
117 | "email": "no.one@ereh.won",
118 | }
119 | }
120 |
121 | #session = requests.session()
122 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
123 |
124 | print("[User:createUser()] POST Request URL: https://" + self.target_url + self.users_Path)
125 | print("[User:createUser()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
126 | r = requests.post("https://" + self.target_url + self.users_Path, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
127 | print("[User:createUser()] STATUS CODE: " + str(r.status_code) )
128 | print("[User:createUser()] RESPONSE:")
129 | if r.text:
130 | res = json.loads(r.text)
131 | print(json.dumps(res,indent=4, separators=(',', ': ')))
132 | else:
133 | print("NONE")
134 |
135 | def getUser(self, token):
136 | print('[User:getUser()] token: ' + token)
137 | #"Authorization: Bearer $token"
138 | authStr = 'Bearer ' + token
139 | print('[User:getUser()] authStr: ' + authStr)
140 | #session = requests.session()
141 | #session.mount('https://', Tls1Adapter()) # remove for production
142 |
143 | print("[User:getUser()] GET Request URL: https://" + self.target_url + self.user_Path+USEREXTERNALID)
144 | print("[User:getUser()] JSON Payload: NONE REQUIRED")
145 | r = requests.get("https://" + self.target_url + self.user_Path+USEREXTERNALID, headers={'Authorization':authStr}, verify=False)
146 |
147 | print("[User:getUser()] STATUS CODE: " + str(r.status_code) )
148 | print("[User:getUser()] RESPONSE:")
149 | if r.text:
150 | res = json.loads(r.text)
151 | print(json.dumps(res,indent=4, separators=(',', ': ')))
152 | else:
153 | print("NONE")
154 |
155 | def updateUser(self, dsk, token):
156 | #"Authorization: Bearer $token"
157 | authStr = 'Bearer ' + token
158 | print("[User:updateUser()] USEREXTERNALID: " + USEREXTERNALID)
159 |
160 | self.PAYLOAD = {
161 | "externalId": USEREXTERNALID,
162 | "dataSourceId": dsk, #self.dskExternalId, Supported soon.
163 | "userName":"python_demo",
164 | "password": "python16",
165 | "availability": {
166 | "available": "Yes"
167 | },
168 | "name": {
169 | "given": "Python",
170 | "family": "BbDN",
171 | "middle": "Demo",
172 | },
173 | "contact": {
174 | "email": "no.one@ereh.won",
175 | }
176 | }
177 | #session = requests.session()
178 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
179 |
180 | print("[User:updateUser()] PATCH Request URL: https://" + self.target_url + self.user_Path+USEREXTERNALID)
181 | print("[User:updateUser()] JSON Payload: " + json.dumps(self.PAYLOAD, indent=4, separators=(',', ': ')))
182 | r = requests.patch("https://" + self.target_url + self.user_Path+USEREXTERNALID, data=json.dumps(self.PAYLOAD), headers={'Authorization':authStr, 'Content-Type':'application/json'}, verify=False)
183 |
184 | print("[User:updateUser()] STATUS CODE: " + str(r.status_code) )
185 | print("[User:updateUser()] RESPONSE:")
186 | if r.text:
187 | res = json.loads(r.text)
188 | print(json.dumps(res,indent=4, separators=(',', ': ')))
189 | else:
190 | print("NONE")
191 |
192 | def deleteUser(self, token):
193 | #"Authorization: Bearer $token"
194 | authStr = 'Bearer ' + token
195 | print("[User:deleteUser()] USEREXTERNALID: " + USEREXTERNALID)
196 |
197 | #session = requests.session()
198 | #session.mount('https://', Tls1Adapter()) # remove for production with commercial cert
199 |
200 | print("[User:deleteUser()] DELETE Request URL: https://" + self.target_url + self.user_Path+USEREXTERNALID)
201 | print("[User:deleteUser()] JSON Payload: NONE REQUIRED")
202 | r = requests.delete("https://" + self.target_url + self.user_Path+USEREXTERNALID, headers={'Authorization':authStr}, verify=False)
203 |
204 | print("[User:deleteUser()] STATUS CODE: " + str(r.status_code) )
205 | print("[User:deleteUser()] RESPONSE:")
206 | if r.text:
207 | res = json.loads(r.text)
208 | print(json.dumps(res,indent=4, separators=(',', ': ')))
209 | else:
210 | print("NONE")
211 |
--------------------------------------------------------------------------------