diff --git a/.github/docker/docker-compose.opencast.yml b/.github/docker/docker-compose.opencast.yml new file mode 100644 index 0000000000000000000000000000000000000000..99bf5fbf5175e0ff31e27b981ab6ba61c1e8d703 --- /dev/null +++ b/.github/docker/docker-compose.opencast.yml @@ -0,0 +1,42 @@ +services: + opencast_opensearch: + image: opensearchproject/opensearch:1 + ports: + - "9200:9200" + environment: + discovery.type: single-node + bootstrap.memory_lock: 'true' + OPENSEARCH_JAVA_OPTS: -Xms128m -Xmx512m + DISABLE_INSTALL_DEMO_CONFIG: 'true' + DISABLE_SECURITY_PLUGIN: 'true' + volumes: + - opencast_opensearch:/usr/share/opensearch/data + + opencast: + image: quay.io/opencast/allinone:16.6 + network_mode: host + environment: + ORG_OPENCASTPROJECT_SERVER_URL: http://127.0.0.1:8081 + ORG_OPENCASTPROJECT_DOWNLOAD_URL: http://127.0.0.1:8081/static + ORG_OPENCASTPROJECT_SECURITY_ADMIN_USER: admin + ORG_OPENCASTPROJECT_SECURITY_ADMIN_PASS: opencast + ORG_OPENCASTPROJECT_SECURITY_DIGEST_USER: opencast_system_account + ORG_OPENCASTPROJECT_SECURITY_DIGEST_PASS: CHANGE_ME + ELASTICSEARCH_SERVER_HOST: localhost + volumes: + - opencast_data:/data + - ./opencast/etc/opencast/security/mh_default_org.xml:/opencast/etc/security/mh_default_org.xml + - ./opencast/etc/opencast/org.opencastproject.kernel.security.OAuthConsumerDetailsService.cfg:/opencast/etc/org.opencastproject.kernel.security.OAuthConsumerDetailsService.cfg + - ./opencast/etc/opencast/org.opencastproject.plugin.impl.PluginManagerImpl.cfg:/opencast/etc/org.opencastproject.plugin.impl.PluginManagerImpl.cfg + - ./opencast/etc/opencast/org.opencastproject.security.lti.LtiLaunchAuthenticationHandler.cfg:/opencast/etc/org.opencastproject.security.lti.LtiLaunchAuthenticationHandler.cfg + - ./opencast/etc/opencast/org.opencastproject.userdirectory.studip-default.cfg:/opencast/etc/org.opencastproject.userdirectory.studip-default.cfg + + opencast_nginx: + image: nginx:1.24 + network_mode: host + volumes: + - ./opencast/etc/nginx/nginx.conf:/etc/nginx/nginx.conf + +volumes: + opencast_opensearch: {} + opencast_data: {} \ No newline at end of file diff --git a/.github/docker/docker-compose.studip.yml b/.github/docker/docker-compose.studip.yml new file mode 100644 index 0000000000000000000000000000000000000000..5dc589128843eb5a0d09fe01c63e0557132c7c49 --- /dev/null +++ b/.github/docker/docker-compose.studip.yml @@ -0,0 +1,43 @@ +services: + studip_db: + image: mariadb:10.4 + volumes: + - studip_db_data:/var/lib/mysql + ports: + - "3306:3306" + command: mysqld --sql_mode="" + restart: always + environment: + MYSQL_RANDOM_ROOT_PASSWORD: 1 + MYSQL_DATABASE: studip_db + MYSQL_USER: studip_user + MYSQL_PASSWORD: studip_password + studip: + image: studip/studip:5.4 + network_mode: host + depends_on: + - studip_db + volumes: + - studip_data:/var/www/studip/data + - ../..:/var/www/studip/public/plugins_packages/elan-ev/OpencastV3 + restart: always + environment: + MYSQL_DATABASE: studip_db + MYSQL_USER: studip_user + MYSQL_PASSWORD: studip_password + MYSQL_HOST: 127.0.0.1 + STUDIP_MAIL_TRANSPORT: debug + + # Use automigrate to migrate your instance on startup + AUTO_MIGRATE: 1 + + # Use proxy url OR autoproxy if run behind a proxy + # PROXY_URL: https://studip.example.com/ + # AUTO_PROXY: 1 + + # Demo data for your studip instance + DEMO_DATA: 1 + +volumes: + studip_data: {} + studip_db_data: {} diff --git a/.github/docker/docker-compose.yml b/.github/docker/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..6f6b9ae93fa6a7ecf7aac56e5bfe74b2b305c7c2 --- /dev/null +++ b/.github/docker/docker-compose.yml @@ -0,0 +1,3 @@ +include: + - docker-compose.opencast.yml + - docker-compose.studip.yml diff --git a/.github/docker/oc.sql b/.github/docker/oc.sql new file mode 100644 index 0000000000000000000000000000000000000000..2455dec35b57856e25d4022a8ec74962854732d6 --- /dev/null +++ b/.github/docker/oc.sql @@ -0,0 +1,174 @@ +SET FOREIGN_KEY_CHECKS=0; + +REPLACE INTO `oc_config` + (`id`, `service_url`, `service_user`, `service_password`, `service_version`, `settings`) VALUES + (1, 'http://127.0.0.1:8081', 'admin', 'opencast', '16.6', '{\"lti_consumerkey\":\"CONSUMERKEY\",\"lti_consumersecret\":\"CONSUMERSECRET\"}'); + + +REPLACE INTO `oc_endpoints` (`config_id`, `service_url`, `service_type`) VALUES +(1, 'http://127.0.0.1:8081/api/events', 'apievents'), +(1, 'http://127.0.0.1:8081/api/playlists', 'apiplaylists'), +(1, 'http://127.0.0.1:8081/api/series', 'apiseries'), +(1, 'http://127.0.0.1:8081/api/workflows', 'apiworkflows'), +(1, 'http://127.0.0.1:8081/capture-admin', 'capture-admin'), +(1, 'http://127.0.0.1:8081/ingest', 'ingest'), +(1, 'http://127.0.0.1:8081/play', 'play'), +(1, 'http://127.0.0.1:8081/recordings', 'recordings'), +(1, 'http://127.0.0.1:8081/search', 'search'), +(1, 'http://127.0.0.1:8081/series', 'series'), +(1, 'http://127.0.0.1:8081/services', 'services'), +(1, 'http://127.0.0.1:8081/upload', 'upload'), +(1, 'http://127.0.0.1:8081/workflow', 'workflow'); + + +REPLACE INTO `config_values` (`field`, `range_id`, `value`, `mkdate`, `chdate`, `comment`) VALUES +('OPENCAST_API_TOKEN', 'studip', 'mytoken1234abcdef', 1693295334, 1693295334, ''); + + +REPLACE INTO `config_values` (`field`, `range_id`, `value`, `mkdate`, `chdate`, `comment`) VALUES +('OPENCAST_DEFAULT_SERVER ', 'studip', '1', 1693295334, 1693295334, ''); + +REPLACE INTO `roles_plugins` (`roleid`, `pluginid`) VALUES +(7, 29); + + +REPLACE INTO `auth_user_md5` (`user_id`, `username`, `password`, `perms`, `Vorname`, `Nachname`, `Email`, `validation_key`, `auth_plugin`, `locked`, `lock_comment`, `locked_by`, `visible`) VALUES +('fad0229f8b0573cda5fbdf5fcfa89362', 'simple_autor', 0x24326124303824756C6A4D587969786C71376939634D6539775841364F526C612E466C6E46743370762F45754E5150356C58516A775070634442462E, 'autor', 'Simple', 'Autor', 'test@studip.de', '', 'standard', 0, NULL, NULL, 'unknown'); + +UPDATE auth_user_md5 SET visible = 'always' WHERE 1; + +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', '205f3efb7997a0fc9755da2b535038da', 1); +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', '6235c46eb9e962866ebdceece739ace5', 1); +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', '76ed43ef286fb55cf9e41beadb484a9f', 1); +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', '7e81ec247c151c02ffd479511e24cc03', 1); +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', 'e7a0a84b161f3e8c09b4a0a2e8a58147', 1); +REPLACE INTO config_values (field, range_id, value) VALUES ('TERMS_ACCEPTED', 'fad0229f8b0573cda5fbdf5fcfa89362', 1); + +-- add videos so foreign keys are working +# REPLACE INTO `oc_video` (`id`, `config_id`, `episode`, `available`, `duration`) VALUES +# (1, 1, 'ID-goat', 1, NULL), +# (2, 1, 'ID-weitsprung', 1, NULL), +# (3, 1, 'ID-nasa-earth-4k', 1, NULL), +# (4, 1, 'ID-strong-river-flowing-down-the-green-forest', 1, NULL), +# (5, 1, 'ID-marguerite', 1, NULL), +# (6, 1, 'ID-espresso-video', 1, NULL), +# (7, 1, 'ID-westerberg', 1, NULL), +# (8, 1, 'ID-cats', 1, NULL), +# (9, 1, 'ID-spring', 1, NULL), +# (10, 1, 'ID-dog-rose', 1, NULL), +# (11, 1, 'ID-nasa-rocket-booster', 1, NULL), +# (12, 1, 'ID-was-ist-chaos', 1, NULL), +# (13, 1, 'ID-3d-print', 1, NULL), +# (14, 1, 'ID-perseverance-arrives-at-mars', 1, NULL), +# (15, 1, 'ID-pendulum-with-spring-damper', 1, NULL), +# (16, 1, 'ID-coffee-run', 1, NULL), +# (17, 1, 'ID-lavender', 1, NULL), +# (18, 1, 'ID-subtitle-demo', 1, NULL), +# (19, 1, 'ID-about-opencast', 1, NULL), +# (20, 1, 'ID-dual-stream-demo', 1, NULL); + + +# REPLACE INTO `oc_video_sync` +# VALUES (1,1,'scheduled','2023-11-10 11:06:02',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (2,2,'scheduled','2023-11-10 11:06:02',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (3,3,'scheduled','2023-11-10 11:06:02',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (4,4,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (5,5,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (6,6,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (7,7,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (8,8,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (9,9,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (10,10,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (11,11,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (12,12,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (13,13,'scheduled','2023-11-10 11:06:03',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (14,14,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (15,15,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (16,16,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (17,17,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (18,18,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (19,19,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'), +# (20,20,'scheduled','2023-11-10 11:06:04',NULL,'0000-00-00 00:00:00','0000-00-00 00:00:00'); + +-- allow test_dozent access to videos +# REPLACE INTO oc_video_user_perms +# (video_id, user_id, perm) VALUES +# (1, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (2, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (3, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (4, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (5, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (6, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (7, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (8, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (9, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (10, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (11, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (12, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (13, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (14, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (15, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (16, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (17, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (18, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (19, '205f3efb7997a0fc9755da2b535038da', 'owner'), +# (20, '205f3efb7997a0fc9755da2b535038da', 'owner'); + +-- activate plugin in course +REPLACE INTO tools_activated + (range_id, range_type, plugin_id, position, metadata, mkdate, chdate) VALUES +('a07535cf2f8a72df33c12ddfa4b53dde', 'course', 29, 11, '[]', 1699267230, 1699267230); + +-- add videos to course playlist +# REPLACE INTO oc_playlist +# (id, token, config_id, service_playlist_id, title, visibility, chdate, mkdate, sort_order, allow_download) VALUES +# (1, 'fce2a63c', 1, 'studip-playlist', '12345 Test Lehrveranstaltung (WS 2023/2024)', NULL, '2023-11-10 12:50:57', '2023-11-10 12:50:57', 'created_desc', NULL); + +# REPLACE INTO `oc_playlist_seminar` (`id`, `playlist_id`, `seminar_id`, `is_default`, `visibility`) VALUES +# (1, 1, 'a07535cf2f8a72df33c12ddfa4b53dde', 1, 'visible'); + +# REPLACE INTO oc_playlist_video +# (playlist_id, video_id, `order`) VALUES +# (1, 1, 0), +# (1, 2, 0), +# (1, 3, 0), +# (1, 4, 0), +# (1, 5, 0), +# (1, 6, 0), +# (1, 7, 0), +# (1, 8, 0), +# (1, 9, 0), +# (1, 10, 0), +# (1, 11, 0), +# (1, 12, 0), +# (1, 13, 0), +# (1, 14, 0), +# (1, 15, 0), +# (1, 16, 0), +# (1, 17, 0), +# (1, 18, 0), +# (1, 19, 0), +# (1, 20, 0); + + +REPLACE INTO `oc_workflow` (`id`, `config_id`, `name`, `tag`, `displayname`) VALUES +(1, 1, 'delete', 'delete', 'Delete'), +(2, 1, 'duplicate-event', 'archive', 'Duplicate Event'), +(3, 1, 'fast', 'schedule', 'Fast Testing Workflow'), +(4, 1, 'fast', 'upload', 'Fast Testing Workflow'), +(5, 1, 'schedule-and-upload', 'schedule', 'Process upon upload and schedule'), +(6, 1, 'schedule-and-upload', 'upload', 'Process upon upload and schedule'), +(7, 1, 'publish', 'archive', 'Publish'), +(8, 1, 'publish', 'editor', 'Publish'), +(9, 1, 'republish-metadata', 'archive', 'Republish metadata'), +(10, 1, 'retract', 'archive', 'Retract'); + + +REPLACE INTO `oc_workflow_config` (`id`, `config_id`, `used_for`, `workflow_id`) VALUES +(1, 1, 'schedule', 5), +(2, 1, 'upload', 6), +(3, 1, 'studio', 6), +(4, 1, 'delete', 1), +(5, 1, 'subtitles', 9); + +SET FOREIGN_KEY_CHECKS=1; diff --git a/.github/docker/opencast/etc/nginx/nginx.conf b/.github/docker/opencast/etc/nginx/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..b24c3f3a1c002603817d73a737af46cd580a679a --- /dev/null +++ b/.github/docker/opencast/etc/nginx/nginx.conf @@ -0,0 +1,79 @@ + +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + #include /etc/nginx/conf.d/*.conf; + + # Do not send the nginx version number in error pages and Server header + server_tokens off; + + server { + + listen 8081; + + # Only send the shortened referrer to a foreign origin, full referrer + # to a local host + # https://infosec.mozilla.org/guidelines/web_security#referrer-policy + add_header Referrer-Policy strict-origin-when-cross-origin; + + # Basic open CORS for everyone + add_header Access-Control-Allow-Origin $http_origin always; + add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always; + add_header Access-Control-Allow-Credentials true always; + add_header Access-Control-Allow-Headers 'Origin,Content-Type,Accept,Authorization' always; + + # Always respond with 200 to OPTIONS requests as browsers do not accept + # non-200 responses to CORS preflight requests. + if ($request_method = OPTIONS) { + return 200; + } + + # Accept large ingests + client_max_body_size 0; + + location / { + + proxy_set_header Host $host:8081; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_pass http://127.0.0.1:8080; + + proxy_cookie_path / "/; HTTPOnly; Secure"; + + + # Do not buffer responses + proxy_buffering off; + + # Do not buffer requests + proxy_request_buffering off; + } + } +} diff --git a/.github/docker/opencast/etc/opencast/org.opencastproject.kernel.security.OAuthConsumerDetailsService.cfg b/.github/docker/opencast/etc/opencast/org.opencastproject.kernel.security.OAuthConsumerDetailsService.cfg new file mode 100755 index 0000000000000000000000000000000000000000..d9c8230f9978506cdf2a3d6971acf640102f12d9 --- /dev/null +++ b/.github/docker/opencast/etc/opencast/org.opencastproject.kernel.security.OAuthConsumerDetailsService.cfg @@ -0,0 +1,8 @@ +# OAuth consumer consisting of name, key and secret. +# +# Multiple OAuth consumers can be configured, by incrementing the counter. The list is read +# sequentially incrementing the counter. If you miss any numbers it will stop looking for +# further consumers. +oauth.consumer.name.1=CONSUMERNAME +oauth.consumer.key.1=CONSUMERKEY +oauth.consumer.secret.1=CONSUMERSECRET diff --git a/.github/docker/opencast/etc/opencast/org.opencastproject.plugin.impl.PluginManagerImpl.cfg b/.github/docker/opencast/etc/opencast/org.opencastproject.plugin.impl.PluginManagerImpl.cfg new file mode 100755 index 0000000000000000000000000000000000000000..81901d4d6ca89515580d3acf65080f9e41698ab4 --- /dev/null +++ b/.github/docker/opencast/etc/opencast/org.opencastproject.plugin.impl.PluginManagerImpl.cfg @@ -0,0 +1,22 @@ +### +# Opencast Plugins +# +# This configuration allows you to turn additional functionality of Opencast off and on. +# Plugins can be enabled at runtime. +## + +# List of available plugins +opencast-plugin-admin-ng = off +opencast-plugin-legacy-annotation = off +opencast-plugin-transcription-services = off +opencast-plugin-userdirectory-brightspace = off +opencast-plugin-userdirectory-canvas = off +opencast-plugin-userdirectory-moodle = off +opencast-plugin-userdirectory-sakai = off +opencast-plugin-userdirectory-studip = on +opencast-plugin-usertracking = off + +# Enables Karaf's verbose feature activateion logs. +# Note that Karaf writes these to stdout, not to the logger. +# Default: false +#verbose = false diff --git a/.github/docker/opencast/etc/opencast/org.opencastproject.security.lti.LtiLaunchAuthenticationHandler.cfg b/.github/docker/opencast/etc/opencast/org.opencastproject.security.lti.LtiLaunchAuthenticationHandler.cfg new file mode 100755 index 0000000000000000000000000000000000000000..8f3723c689e39878e106d4fba60365ada386c15c --- /dev/null +++ b/.github/docker/opencast/etc/opencast/org.opencastproject.security.lti.LtiLaunchAuthenticationHandler.cfg @@ -0,0 +1,98 @@ +# OAuth consumer keys with should be highly trusted. +# +# By default OAuth consumer are regarded as untrusted and a user authenticating via such +# systems receives a rewritten username in the form of "lti:{ltiConsumerGUID}:{ltiUserID}". +# This user is regarded as a new user temporarily existing for the duration of the session. +# Opencast roles associated with the original user will not be attached to this user. +# +# Usernames of users authenticating via highly trusted systems will not be rewritten except +# for the cases configured in the additional options below. +# +# Note that marking a consumer key as highly trusted can be a security risk. If the usernames of sensitive Opencast +# users are not blacklisted, the LMS administrator could create LMS users with the same username and use LTI to grant +# that user access to Opencast. In the default configuration, that includes the `admin` and `opencast_system_account` +# users. +# +# Multiple consumer keys can be configured, by incrementing the counter. The list is read +# sequentially incrementing the counter. If you miss any numbers it will stop looking for +# further consumer keys. +#lti.oauth.highly_trusted_consumer_key.1=CONSUMERKEY + +# Allow the Opencast system administrator user to authenticate as such via LTI. +# +# Note that this user may still authenticate via LTI, but the username will be rewritten, +# even if a trusted OAuth consumer key is used. +# +# Note that this option does not apply to custom users having the ROLE_ADMIN. Use the +# blacklist below instead. +# +# Default: false +#lti.allow_system_administrator=false + +# Allow the Opencast digest user to authenticate as such via LTI. +# +# Note that this user may still authenticate via LTI, but the username will be rewritten, +# even if a trusted OAuth consumer key is used. +# +# Default: false +#lti.allow_digest_user=false + +# A blacklist of users not allowed to authenticate via LTI as themselves. +# +# Note that these users may still authenticate via LTI, but their username will be rewritten, +# even if a trusted OAuth consumer key is used. +# +# Multiple users can be configured, by incrementing the counter. The list is read sequentially +# incrementing the counter. If you miss any numbers it will stop looking for further users. +# +# Default: no blacklisted users +#lti.blacklist.user.1= + +# Determines whether a JpaUserReference should be created on LTI User Login. +# This persists the LTI Users in the database, giving them the ability to create long running tasks like ingesting a video. +# +# Default: true +lti.create_jpa_user_reference = false + +# Determines which LTI roles should be persisted in the database on LTI user logins. +# The "lti.create_jpa_user_reference" config key has to be "true", otherwise this config key will be ignored. +# The value can be a list of LTI roles identifying users to be persisted or the special value * causing all users to be persisted. +# The value is not case sensitive. +# +# Examples: +# - Persist only instructors: +# lti.create_jpa_user_reference.roles = instructor +# - Persist only instructors and administrators: +# lti.create_jpa_user_reference.roles = instructor, administrator +# - Persist all users: +# lti.create_jpa_user_reference.roles = * +# +# Default: * +# +# lti.create_jpa_user_reference.roles = * + +# Add Custom Roles to users who has the role with custom_role_name +# This configuration key is a list, to add additional custom roles increment the lti.custom_role_name.# number, +# the role will only be added if it has matching lti.custom_roles.# roles configuration +# It also has support for regex patterns for example 'ims\/lis\/.*' will match all roles that start with ims/list/ +# Default: empty no custom roles + +lti.custom_role_name.1=Instructor + +# The lti.custom_roles.# configuration key must have matching lti.custom_role_name.# key. +# This Role set is an example for a user which can open the editor for an event and upload videos via opencast studio. +lti.custom_roles.1=ROLE_STUDIO,ROLE_UI_EVENTS_DETAILS_COMMENTS_CREATE,ROLE_UI_EVENTS_DETAILS_COMMENTS_DELETE,ROLE_UI_EVENTS_DETAILS_COMMENTS_EDIT,ROLE_UI_EVENTS_DETAILS_COMMENTS_REPLY,ROLE_UI_EVENTS_DETAILS_COMMENTS_RESOLVE,ROLE_UI_EVENTS_DETAILS_COMMENTS_VIEW,ROLE_UI_EVENTS_DETAILS_MEDIA_VIEW,ROLE_UI_EVENTS_DETAILS_METADATA_EDIT,ROLE_UI_EVENTS_DETAILS_METADATA_VIEW,ROLE_UI_EVENTS_DETAILS_VIEW,ROLE_UI_EVENTS_EDITOR_EDIT,ROLE_UI_EVENTS_EDITOR_VIEW,ROLE_CAPTURE_AGENT,ROLE_API_EVENTS_TRACK_EDIT,ROLE_API_WORKFLOW_INSTANCE_CREATE + +# Prefix for LTI context based roles based on OAuth consumer keys. +# The LTI context (e.g. the course identifier) is used to generate context roles like “12345_Learner”. +# If multiple LTI consumers are used, this can clash, causing users from one consumer to get access to content from +# another consumer. The prefix can be used to prevent this by generating context roles like “PREFIX1_123_Learner” and +# “PREFIX2_123_Learner” instead. +# +# Context roles may not start with “ROLE_”. Avoid using that as a prefix. +# +# Default: No prefix +# +#lti.consumer_role_prefix.CONSUMERKEY0 = STUDIP_ +#lti.consumer_role_prefix.CONSUMERKEY1 = MOODLE_ +#lti.consumer_role_prefix.CONSUMERKEY2 = ILIAS_ diff --git a/.github/docker/opencast/etc/opencast/org.opencastproject.userdirectory.studip-default.cfg b/.github/docker/opencast/etc/opencast/org.opencastproject.userdirectory.studip-default.cfg new file mode 100755 index 0000000000000000000000000000000000000000..f02a7cc829f77290d2dd3055980223dba2dff485 --- /dev/null +++ b/.github/docker/opencast/etc/opencast/org.opencastproject.userdirectory.studip-default.cfg @@ -0,0 +1,20 @@ +# Stud.IP UserDirectoryProvider configuration + +# This is an optional plugin not enabled by default. +# Enable this plugin in: +# org.opencastproject.plugin.impl.PluginManagerImpl.cfg + +# The organization for this provider +org.opencastproject.userdirectory.studip.org=mh_default_org + +# The URL and token for the Studip REST webservice +org.opencastproject.userdirectory.studip.url=http://localhost/plugins.php/opencastv3/api/ +org.opencastproject.userdirectory.studip.token=mytoken1234abcdef + +# The maximum number of users to cache +# Default: 1000 +#org.opencastproject.userdirectory.studip.cache.size=1000 + +# The maximum number of minutes to cache a user +# Default: 60 +org.opencastproject.userdirectory.studip.cache.expiration=1 diff --git a/.github/docker/opencast/etc/opencast/security/mh_default_org.xml b/.github/docker/opencast/etc/opencast/security/mh_default_org.xml new file mode 100755 index 0000000000000000000000000000000000000000..e28c7c6d7d361f9336ff535c53d29f3192901313 --- /dev/null +++ b/.github/docker/opencast/etc/opencast/security/mh_default_org.xml @@ -0,0 +1,919 @@ +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:sec="http://www.springframework.org/schema/security" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:osgi="http://www.springframework.org/schema/osgi" + xmlns:util="http://www.springframework.org/schema/util" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/util + http://www.springframework.org/schema/util/spring-util-3.1.xsd + http://www.springframework.org/schema/osgi + http://www.springframework.org/schema/osgi/spring-osgi.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-3.1.xsd"> + + <!-- ######################################## --> + <!-- # Open and unsecured url patterns # --> + <!-- ######################################## --> + + <sec:http pattern="/favicon.ico" security="none" /> + + <sec:http create-session="ifRequired" servlet-api-provision="true" realm="Opencast" + entry-point-ref="opencastEntryPoint"> + + <sec:access-denied-handler error-page="/403.html"/> + + <!-- ################ --> + <!-- # URL SECURITY # --> + <!-- ################ --> + + <!-- Allow anonymous access to the login form --> + <sec:intercept-url pattern="/" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/login.html" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/sysinfo/bundles/version" method="GET" access="ROLE_ANONYMOUS" /> + + <!-- Allow anonymous access to resources from runtime info ui --> + <sec:intercept-url pattern="/img/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/js/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/scripts/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/styles/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/docs.*" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/rest_docs.*" access="ROLE_ANONYMOUS" /> + + <!-- Allow anonymous access to the player redirect --> + <sec:intercept-url pattern="/play/*" access="ROLE_ANONYMOUS" /> + + <!-- Define roles for metrics endpoint --> + <sec:intercept-url pattern="/metrics" access="ROLE_ADMIN, ROLE_METRICS" /> + + <!-- Protect admin UI facade --> + <sec:intercept-url pattern="/workflow/definitions.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/acl/acls.json" method="GET" access="ROLE_ADMIN, ROLE_UI_ACLS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/acl/*" method="GET" access="ROLE_ADMIN, ROLE_UI_ACLS_VIEW" /> + <sec:intercept-url pattern="/acl-manager/acl/*" method="GET" access="ROLE_ADMIN, ROLE_UI_ACLS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/capture-agents/agents.json" method="GET" access="ROLE_ADMIN, ROLE_UI_LOCATIONS_VIEW, ROLE_UI_EVENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/capture-agents/*" method="GET" access="ROLE_ADMIN, ROLE_UI_LOCATIONS_DETAILS_CAPABILITIES_VIEW, ROLE_UI_LOCATIONS_DETAILS_CONFIGURATION_VIEW, ROLE_UI_LOCATIONS_DETAILS_GENERAL_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/asset/assets.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/asset/attachment/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/asset/catalog/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/asset/media/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/asset/publication/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/catalogAdapters" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_METADATA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/events.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/workflowProperties" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_CREATE, ROLE_UI_TASKS_CREATE, ROLE_UI_EVENTS_EDITOR_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/new/metadata" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_METADATA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/new/processing" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_CREATE, ROLE_UI_TASKS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/*/attachments.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ATTACHMENTS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/comments" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/publications.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_PUBLICATIONS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/hasActiveTransaction" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/hasSnapshots.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/*/media.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_MEDIA_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/media/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_MEDIA_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/metadata.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_METADATA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/scheduling.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_SCHEDULING_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/operations.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/operations/*" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/errors.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/errors/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_VIEW"/> + <sec:intercept-url pattern="/admin-ng/event/*/access.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ACL_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/groups/groups.json" method="GET" access="ROLE_ADMIN, ROLE_UI_GROUPS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/groups/*" method="GET" access="ROLE_ADMIN, ROLE_UI_GROUPS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/index/rebuild/states.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERVICES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/job/jobs.json" method="GET" access="ROLE_ADMIN, ROLE_UI_JOBS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/series.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/new/metadata" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_METADATA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/new/themes" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_THEMES_EDIT" /> + <sec:intercept-url pattern="/admin-ng/series/new/tobira/page" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_TOBIRA_EDIT" /> + <sec:intercept-url pattern="/admin-ng/series/*/access.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_ACL_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/*/metadata.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_METADATA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/*/theme.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_THEMES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/*/tobira/pages" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_TOBIRA_VIEW" /> + <sec:intercept-url pattern="/admin-ng/server/servers.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERVERS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/*/hasEvents.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/series/configuration.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/services/services.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERVICES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/tasks/processing.json" method="GET" access="ROLE_ADMIN, ROLE_UI_TASKS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/themes/themes.json" method="GET" access="ROLE_ADMIN, ROLE_UI_THEMES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/themes/*/usage.json" method="GET" access="ROLE_ADMIN, ROLE_UI_THEMES_DETAILS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/themes/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_THEMES_DETAILS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/tools/*/editor.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_EDITOR_VIEW" /> + <sec:intercept-url pattern="/admin-ng/tools/*/thumbnail.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_EDITOR_VIEW" /> + <sec:intercept-url pattern="/admin-ng/tools/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_EDITOR_VIEW" /> + <sec:intercept-url pattern="/admin-ng/users/users.json" method="GET" access="ROLE_ADMIN, ROLE_UI_USERS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/users/*.json" method="GET" access="ROLE_ADMIN, ROLE_UI_USERS_EDIT, ROLE_UI_EVENTS_DETAILS_ACL_USER_ROLES_VIEW, ROLE_UI_SERIES_DETAILS_ACL_USER_ROLES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/user-settings/signature" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/resources/events/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/jobs/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_JOBS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/series/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERIES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/servers/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERVERS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/services/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_SERVICES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/themes/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_THEMES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/recordings/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_LOCATIONS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/users/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_USERS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/groups/filters.json" method="GET" access="ROLE_ADMIN, ROLE_UI_GROUPS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/components.json" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/resources/providers.json" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/resources/THEMES.json" method="GET" access="ROLE_ADMIN, ROLE_UI_THEMES_VIEW" /> + <sec:intercept-url pattern="/admin-ng/resources/*.json" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/statistics/*.json" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/statistics/export.csv" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/assets/assets/**" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_VIEW" /> + <sec:intercept-url pattern="/admin-ng/adopter/**" method="GET" access="ROLE_ADMIN" /> + + <sec:intercept-url pattern="/admin-ng/acl/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_ACLS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/metadata" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_METADATA_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/scheduling" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_SCHEDULING_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/action/stop" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/action/retry" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*/action/none" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_EDIT"/> + <sec:intercept-url pattern="/admin-ng/event/bulk/update" method="PUT" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_SCHEDULING_EDIT" /> + <sec:intercept-url pattern="/admin-ng/groups/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_GROUPS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/series/*/metadata" method="PUT" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_METADATA_EDIT" /> + <sec:intercept-url pattern="/admin-ng/series/*/theme" method="PUT" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_THEMES_EDIT" /> + <sec:intercept-url pattern="/admin-ng/themes/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_THEMES_EDIT" /> + <sec:intercept-url pattern="/admin-ng/users/*" method="PUT" access="ROLE_ADMIN, ROLE_UI_USERS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/user-settings/signature/*" method="PUT" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/adopter/**" method="PUT" access="ROLE_ADMIN" /> + + <sec:intercept-url pattern="/services/maintenance" method="POST" access="ROLE_ADMIN, ROLE_UI_SERVERS_MAINTENANCE_EDIT" /> + <sec:intercept-url pattern="/services/sanitize" method="POST" access="ROLE_ADMIN, ROLE_UI_SERVICES_STATUS_EDIT" /> + <sec:intercept-url pattern="/staticfiles" method="POST" access="ROLE_ADMIN, ROLE_UI_THEMES_CREATE, ROLE_UI_THEMES_EDIT" /> + <sec:intercept-url pattern="/admin-ng/acl" method="POST" access="ROLE_ADMIN, ROLE_UI_ACLS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/bulk/conflicts" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_SCHEDULING_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/deleteEvents" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/events/metadata.json" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_METADATA_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/new" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/new/conflicts" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/scheduling.json" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_SCHEDULING_VIEW" /> + <sec:intercept-url pattern="/admin-ng/event/*/access" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ACL_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/assets" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_ASSETS_EDIT" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*/reply" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/groups" method="POST" access="ROLE_ADMIN, ROLE_UI_GROUPS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/series/deleteSeries" method="POST" access="ROLE_ADMIN, ROLE_UI_SERIES_DELETE" /> + <sec:intercept-url pattern="/admin-ng/series/new" method="POST" access="ROLE_ADMIN, ROLE_UI_SERIES_CREATE" /> + <sec:intercept-url pattern="/admin-ng/series/*/access" method="POST" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_ACL_EDIT" /> + <sec:intercept-url pattern="/admin-ng/statistics/data.json" method="POST" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/tasks/new" method="POST" access="ROLE_ADMIN, ROLE_UI_TASKS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/themes" method="POST" access="ROLE_ADMIN, ROLE_UI_THEMES_CREATE" /> + <sec:intercept-url pattern="/admin-ng/tools/*/editor.json" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_EDITOR_EDIT" /> + <sec:intercept-url pattern="/admin-ng/tools/*/thumbnail.json" method="POST" access="ROLE_ADMIN, ROLE_UI_EVENTS_EDITOR_EDIT" /> + <sec:intercept-url pattern="/admin-ng/users" method="POST" access="ROLE_ADMIN, ROLE_UI_USERS_CREATE" /> + <sec:intercept-url pattern="/admin-ng/user-settings/signature" method="POST" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/adopter/**" method="POST" access="ROLE_ADMIN" /> + + <sec:intercept-url pattern="/admin-ng/acl/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_ACLS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/capture-agents/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_LOCATIONS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/*/comment/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_COMMENTS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/*/workflows/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_EVENTS_DETAILS_WORKFLOWS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/event/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_EVENTS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/groups/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_GROUPS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/series/*/theme" method="DELETE" access="ROLE_ADMIN, ROLE_UI_SERIES_DETAILS_THEMES_EDIT" /> + <sec:intercept-url pattern="/admin-ng/series/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_SERIES_DELETE" /> + <sec:intercept-url pattern="/admin-ng/themes/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_THEMES_DELETE" /> + <sec:intercept-url pattern="/admin-ng/users/*" method="DELETE" access="ROLE_ADMIN, ROLE_UI_USERS_DELETE" /> + <sec:intercept-url pattern="/admin-ng/adopter/**" method="DELETE" access="ROLE_ADMIN" /> + + <!-- Securing the URLs for the external API interface --> + <!-- External API GET Endpoints --> + <sec:intercept-url pattern="/api" method="GET" access="ROLE_ADMIN, ROLE_API"/> + <sec:intercept-url pattern="/api/agents" method="GET" access="ROLE_ADMIN, ROLE_API_CAPTURE_AGENTS_VIEW"/> + <sec:intercept-url pattern="/api/agents/*" method="GET" access="ROLE_ADMIN, ROLE_API_CAPTURE_AGENTS_VIEW"/> + <sec:intercept-url pattern="/api/events" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_VIEW"/> + <sec:intercept-url pattern="/api/events/*" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_VIEW"/> + <sec:intercept-url pattern="/api/events/*/acl" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_ACL_VIEW"/> + <sec:intercept-url pattern="/api/events/*/media" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_MEDIA_VIEW"/> + <sec:intercept-url pattern="/api/events/*/media/*" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_MEDIA_VIEW"/> + <sec:intercept-url pattern="/api/events/*/metadata" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_VIEW"/> + <sec:intercept-url pattern="/api/events/*/metadata/*" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_VIEW"/> + <sec:intercept-url pattern="/api/events/*/publications" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_PUBLICATIONS_VIEW"/> + <sec:intercept-url pattern="/api/events/*/publications/*" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_PUBLICATIONS_VIEW"/> + <sec:intercept-url pattern="/api/events/*/scheduling" method="GET" access="ROLE_ADMIN, ROLE_API_EVENTS_SCHEDULING_VIEW"/> + <sec:intercept-url pattern="/api/groups" method="GET" access="ROLE_ADMIN, ROLE_API_GROUPS_VIEW"/> + <sec:intercept-url pattern="/api/groups/*" method="GET" access="ROLE_ADMIN, ROLE_API_GROUPS_VIEW"/> + <sec:intercept-url pattern="/api/info/*" method="GET" access="ROLE_ADMIN, ROLE_API" /> + <sec:intercept-url pattern="/api/info/me/*" method="GET" access="ROLE_ADMIN, ROLE_API" /> + <sec:intercept-url pattern="/api/info/organization/properties/engageuiurl" method="GET" access="ROLE_ADMIN, ROLE_UI_EVENTS_EMBEDDING_CODE_VIEW" /> + <sec:intercept-url pattern="/api/listproviders" method="GET" access="ROLE_ADMIN, ROLE_API_LISTPROVIDERS_VIEW"/> + <sec:intercept-url pattern="/api/listproviders/*" method="GET" access="ROLE_ADMIN, ROLE_API_LISTPROVIDERS_VIEW"/> + <sec:intercept-url pattern="/api/playlists" method="GET" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_VIEW"/> + <sec:intercept-url pattern="/api/playlists/*" method="GET" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_VIEW"/> + <sec:intercept-url pattern="/api/series" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_VIEW"/> + <sec:intercept-url pattern="/api/series/*" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_VIEW"/> + <sec:intercept-url pattern="/api/series/*/acl" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_ACL_VIEW"/> + <sec:intercept-url pattern="/api/series/*/metadata" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_VIEW"/> + <sec:intercept-url pattern="/api/series/*/metadata/*" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_VIEW"/> + <sec:intercept-url pattern="/api/series/*/properties" method="GET" access="ROLE_ADMIN, ROLE_API_SERIES_PROPERTIES_VIEW"/> + <sec:intercept-url pattern="/api/statistics/providers" method="GET" access="ROLE_ADMIN, ROLE_API_STATISTICS_VIEW"/> + <sec:intercept-url pattern="/api/statistics/providers/*" method="GET" access="ROLE_ADMIN, ROLE_API_STATISTICS_VIEW"/> + <sec:intercept-url pattern="/api/version" method="GET" access="ROLE_ADMIN, ROLE_API"/> + <sec:intercept-url pattern="/api/version/*" method="GET" access="ROLE_ADMIN, ROLE_API"/> + <sec:intercept-url pattern="/api/workflows" method="GET" access="ROLE_ADMIN, ROLE_API_WORKFLOW_INSTANCE_VIEW"/> + <sec:intercept-url pattern="/api/workflows/*" method="GET" access="ROLE_ADMIN, ROLE_API_WORKFLOW_INSTANCE_VIEW"/> + <sec:intercept-url pattern="/api/workflow-definitions" method="GET" access="ROLE_ADMIN, ROLE_API_WORKFLOW_DEFINITION_VIEW"/> + <sec:intercept-url pattern="/api/workflow-definitions/*" method="GET" access="ROLE_ADMIN, ROLE_API_WORKFLOW_DEFINITION_VIEW"/> + <!-- External API PUT Endpoints --> + <sec:intercept-url pattern="/api/events/*" method="PUT" access="ROLE_ADMIN, ROLE_API_EVENTS_EDIT"/> + <sec:intercept-url pattern="/api/events/*/acl" method="PUT" access="ROLE_ADMIN, ROLE_API_EVENTS_ACL_EDIT"/> + <sec:intercept-url pattern="/api/events/*/metadata" method="PUT" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_EDIT"/> + <sec:intercept-url pattern="/api/events/*/metadata/*" method="PUT" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_EDIT"/> + <sec:intercept-url pattern="/api/events/*/scheduling" method="PUT" access="ROLE_ADMIN, ROLE_API_EVENTS_SCHEDULING_EDIT"/> + <sec:intercept-url pattern="/api/events/*/track" method="POST" access="ROLE_ADMIN, ROLE_API_EVENTS_TRACK_EDIT"/> + <sec:intercept-url pattern="/api/groups/*" method="PUT" access="ROLE_ADMIN, ROLE_API_GROUPS_EDIT"/> + <sec:intercept-url pattern="/api/playlists/*" method="PUT" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_CREATE, ROLE_API_PLAYLISTS_EDIT"/> + <sec:intercept-url pattern="/api/series/*" method="PUT" access="ROLE_ADMIN, ROLE_API_SERIES_EDIT"/> + <sec:intercept-url pattern="/api/series/*/acl" method="PUT" access="ROLE_ADMIN, ROLE_API_SERIES_ACL_EDIT"/> + <sec:intercept-url pattern="/api/series/*/metadata" method="PUT" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_EDIT"/> + <sec:intercept-url pattern="/api/series/*/metadata/*" method="PUT" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_EDIT"/> + <sec:intercept-url pattern="/api/series/*/properties" method="PUT" access="ROLE_ADMIN, ROLE_API_SERIES_PROPERTIES_EDIT"/> + <sec:intercept-url pattern="/api/workflows/*" method="PUT" access="ROLE_ADMIN, ROLE_API_WORKFLOW_INSTANCE_EDIT"/> + <!-- External API POST Endpoints --> + <sec:intercept-url pattern="/api/events" method="POST" access="ROLE_ADMIN, ROLE_API_EVENTS_CREATE"/> + <sec:intercept-url pattern="/api/events/*" method="POST" access="ROLE_ADMIN, ROLE_API_EVENTS_EDIT"/> + <sec:intercept-url pattern="/api/events/*/acl/*" method="POST" access="ROLE_ADMIN, ROLE_API_EVENTS_ACL_EDIT"/> + <sec:intercept-url pattern="/api/groups" method="POST" access="ROLE_ADMIN, ROLE_API_GROUPS_CREATE"/> + <sec:intercept-url pattern="/api/groups/*/members" method="POST" access="ROLE_ADMIN, ROLE_API_GROUPS_EDIT"/> + <sec:intercept-url pattern="/api/clearIndex" method="POST" access="ROLE_ADMIN"/> + <sec:intercept-url pattern="/api/recreateIndex" method="POST" access="ROLE_ADMIN"/> + <sec:intercept-url pattern="/api/recreateIndex/*" method="POST" access="ROLE_ADMIN"/> + <sec:intercept-url pattern="/api/playlists/*" method="POST" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_CREATE, ROLE_API_PLAYLISTS_EDIT"/> + <sec:intercept-url pattern="/api/playlists/*/entries" method="POST" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_CREATE, ROLE_API_PLAYLISTS_EDIT"/> + <sec:intercept-url pattern="/api/series" method="POST" access="ROLE_ADMIN, ROLE_API_SERIES_CREATE"/> + <sec:intercept-url pattern="/api/security/sign" method="POST" access="ROLE_ADMIN, ROLE_API_SECURITY_EDIT"/> + <sec:intercept-url pattern="/api/statistics/data/query" method="POST" access="ROLE_ADMIN, ROLE_API_STATISTICS_VIEW"/> + <sec:intercept-url pattern="/api/statistics/data/export.csv" method="POST" access="ROLE_ADMIN, ROLE_API_STATISTICS_VIEW"/> + <sec:intercept-url pattern="/api/workflows" method="POST" access="ROLE_ADMIN, ROLE_API_WORKFLOW_INSTANCE_CREATE"/> + <!-- External API DELETE Endpoints --> + <sec:intercept-url pattern="/api/events/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_EVENTS_DELETE"/> + <sec:intercept-url pattern="/api/events/*/acl/*/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_EVENTS_ACL_DELETE"/> + <sec:intercept-url pattern="/api/events/*/metadata" method="DELETE" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_DELETE"/> + <sec:intercept-url pattern="/api/events/*/metadata/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_EVENTS_METADATA_DELETE"/> + <sec:intercept-url pattern="/api/groups/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_GROUPS_DELETE"/> + <sec:intercept-url pattern="/api/groups/*/members/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_GROUPS_EDIT"/> + <sec:intercept-url pattern="/api/playlists/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_PLAYLISTS_DELETE"/> + <sec:intercept-url pattern="/api/series/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_SERIES_DELETE"/> + <sec:intercept-url pattern="/api/series/*/metadata" method="DELETE" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_DELETE"/> + <sec:intercept-url pattern="/api/series/*/metadata/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_SERIES_METADATA_DELETE"/> + <sec:intercept-url pattern="/api/workflows/*" method="DELETE" access="ROLE_ADMIN, ROLE_API_WORKFLOW_INSTANCE_DELETE"/> + + <!-- Enable anonymous access to the admin ui --> + <sec:intercept-url pattern="/admin-ng/fonts/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/img/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/lib/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/modules/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/public/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/scripts/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/shared/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ng/styles/**" access="ROLE_ANONYMOUS" /> + + <sec:intercept-url pattern="/admin-ui/static/media/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ui/static/js/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/admin-ui/static/css/**" access="ROLE_ANONYMOUS" /> + + <!-- Enable anonymous access to the /info/** resource --> + <sec:intercept-url pattern="/info/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/i18n/languages.json" method="GET" access="ROLE_ANONYMOUS" /> + + <!-- anonymous access to user interface configuration --> + <sec:intercept-url pattern="/ui/config/**" access="ROLE_ANONYMOUS" /> + + <!-- Paella player 7 --> + <sec:intercept-url pattern="/paella7/ui/auth.html" access="ROLE_USER" /> + <sec:intercept-url pattern="/paella7/ui/**" access="ROLE_ANONYMOUS" /> + + <!-- Enable anonymous access to the engage player and the GET endpoints it requires --> + <sec:intercept-url pattern="/engage/ui/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/engage/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/engage-player/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/search/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/usertracking/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/usertracking/**" method="PUT" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/static/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/export/**" method="GET" access="ROLE_ANONYMOUS" /> + + <!-- Enable access to Opencast Studio --> + <!-- Admins can access it, as can users with 'ROLE_STUDIO'. A few static --> + <!-- files are also accessible to everyone: 'manifest.json' and source maps --> + <!-- are requested without cookies by most browsers. To prevent random --> + <!-- errors, we make those files public. They do not contain any secrets. --> + <sec:intercept-url pattern="/studio/manifest.json" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/studio/static/js/**" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/studio/**" access="ROLE_ADMIN, ROLE_STUDIO" /> + <sec:intercept-url pattern="/studio-api/**" access="ROLE_ADMIN, ROLE_STUDIO" /> + + <!-- Enable access to Opencast Editor --> + <sec:intercept-url pattern="/editor/**" access="ROLE_ADMIN, ROLE_USER" /> + <sec:intercept-url pattern="/editor-ui/**" access="ROLE_ADMIN, ROLE_USER" /> + + <!-- Enable access to the Tobira module --> + <sec:intercept-url pattern="/tobira/**" access="ROLE_ADMIN" /> + <sec:intercept-url pattern="/tobira/version" access="ROLE_ANONYMOUS" /> + + <!-- Enable anonymous access to the annotation and the series endpoints --> + <sec:intercept-url pattern="/series/**" method="GET" access="ROLE_ANONYMOUS, ROLE_CAPTURE_AGENT" /> + <sec:intercept-url pattern="/annotation/**" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/annotation/**" method="PUT" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/playlists/**" access="ROLE_ANONYMOUS" /> + + <!-- Enable anonymous access to the rss and atom feeds --> + <sec:intercept-url pattern="/feeds/**" method="GET" access="ROLE_ANONYMOUS" /> + + <!-- Secure the system management URLs for admins only --> + <sec:intercept-url pattern="/services/available.*" method="GET" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT" /> + <sec:intercept-url pattern="/services/**" access="ROLE_ADMIN"/> + <sec:intercept-url pattern="/signing/**" access="ROLE_ADMIN" /> + <sec:intercept-url pattern="/system/**" access="ROLE_ADMIN" /> + <sec:intercept-url pattern="/config/**" access="ROLE_ADMIN" /> + + <!-- Enable capture agent updates and ingest --> + <sec:intercept-url pattern="/capture-admin/**" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT" /> + <sec:intercept-url pattern="/recordings/**" method="GET" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT" /> + <sec:intercept-url pattern="/ingest/**" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT, ROLE_STUDIO" /> + <sec:intercept-url pattern="/workflow/definitions.xml" method="GET" access="ROLE_ADMIN, ROLE_CAPTURE_AGENT" /> + + <!-- Secure the user management URLs for admins only --> + <sec:intercept-url pattern="/users/**" access="ROLE_ADMIN" /> + <sec:intercept-url pattern="/admin/users.html" access="ROLE_ADMIN" /> + + <!-- Enable 2-legged OAuth access ("signed fetch") to the LTI launch servlet --> + <sec:intercept-url pattern="/lti/**" access="ROLE_OAUTH_USER" /> + + <!-- Enable access to the LTI tools --> + <sec:intercept-url pattern="/ltitools/**" access="ROLE_OAUTH_USER" /> + + <!-- Enable access to the LTI service --> + <sec:intercept-url pattern="/lti-service-gui/**" access="ROLE_OAUTH_USER" /> + <sec:intercept-url pattern="/lti-service/**" access="ROLE_ADMIN, ROLE_OAUTH_USER" /> + + <sec:intercept-url pattern="/transcripts/watson/results*" method="GET" access="ROLE_ANONYMOUS" /> + <sec:intercept-url pattern="/transcripts/watson/results*" method="POST" access="ROLE_ANONYMOUS" /> + + <!-- Enable access to the redirect routes --> + <sec:intercept-url pattern="/redirect/**" access="ROLE_ANONYMOUS" /> + + <!-- Everything else is for the admin users --> + <sec:intercept-url pattern="/admin-ui" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ui/" method="GET" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ui/index.html" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/admin-ng/index.html" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/index.html" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/index.js" access="ROLE_ADMIN, ROLE_ADMIN_UI" /> + <sec:intercept-url pattern="/**" access="ROLE_ADMIN" /> + + <!-- ############################# --> + <!-- # LOGIN / LOGOUT MECHANISMS # --> + <!-- ############################# --> + + <!-- Uncomment to enable x509 client certificates for identifying clients --> + <!-- sec:x509 subject-principal-regex="CN=(.*?)," user-service-ref="userDetailsService" / --> + + <!-- Enable and configure the failure URL for form-based logins --> + <!-- CAS Auth: Comment this if using CAS authentication --> + <sec:form-login authentication-failure-url="/login.html?error" authentication-success-handler-ref="authSuccessHandler" /> + + <!-- (Pre-)Authentication filter chain(s) --> + <sec:custom-filter position="BASIC_AUTH_FILTER" ref="authenticationFilters" /> + <sec:custom-filter position="PRE_AUTH_FILTER" ref="preAuthenticationFilters" /> + + <sec:custom-filter ref="asyncTimeoutRedirectFilter" after="EXCEPTION_TRANSLATION_FILTER"/> + + <!-- Opencast is shipping its own implementation of the anonymous filter --> + <sec:custom-filter ref="anonymousFilter" position="ANONYMOUS_FILTER" /> + + <!-- CAS Auth: Uncomment this if using CAS authentication + <sec:custom-filter position="FORM_LOGIN_FILTER" ref="casFilter" /> + --> + + <!-- Enables "remember me" functionality --> + <sec:remember-me services-ref="rememberMeServices" /> + + <!-- Set the request cache --> + <sec:request-cache ref="requestCache" /> + + <!-- If any URLs are to be exposed to anonymous users, the "sec:anonymous" filter must be present --> + <sec:anonymous enabled="false" /> + + <!-- CAS Auth: Uncomment this if using CAS authentication --> + <!-- Enables CAS Single Sign Out + <sec:logout logout-success-url="/cas-logout.jsp"/> + <sec:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> + <sec:custom-filter ref="singleLogoutFilter" before="FORM_LOGIN_FILTER"/> + --> + + <!-- Enables log out --> + <sec:logout success-handler-ref="logoutSuccessHandler" /> + + <!-- JWT single log out + Please specify the URL to return to after logging out. Comment out the logoutSuccessHandler above. + <sec:logout logout-success-url="https://auth.example.org/sign_out?rd=http://www.opencast.org" /> + --> + + </sec:http> + + <bean id="rememberMeServices" class="org.opencastproject.kernel.security.SystemTokenBasedRememberMeService"> + <property name="userDetailsService" ref="userDetailsService"/> + <!-- All following settings are optional --> + <property name="tokenValiditySeconds" value="1209600"/> + <property name="cookieName" value="oc-remember-me"/> + <!-- The following key will be augmented by system properties if left at the default value. + Thus, leaving this untouched is okay. This key must be equal to the key passed to + rememberMeAuthenticationProvider (s. below). To generate cookies which are valid for the whole cluster, + set this manually. The key won't be augmented/randomized if you use something different than 'opencast'. --> + <property name="key" value="opencast"/> + </bean> + + <bean id="rememberMeAuthenticationProvider" + class="org.opencastproject.kernel.security.SystemTokenBasedRememberMeAuthenticationProvider"> + <!-- This key must be equal to the key passed to rememberMeServices (s. above) --> + <property name="key" value="opencast"/> + </bean> + + <!-- ############################# --> + <!-- # Authentication Filters # --> + <!-- ############################# --> + + <bean id="authenticationFilters" class="org.springframework.web.filter.CompositeFilter"> + <property name="filters"> + <list> + <!-- Digest auth is used by capture agents and is used to enable transparent clustering of services --> + <!-- ATTENTION! Do not deactivate the digest filter, otherwise the distributed setup would not work --> + <ref bean="digestFilter" /> + + <!-- Basic authentication --> + <ref bean="basicAuthenticationFilter" /> + + <!-- 2-legged OAuth is used by trusted 3rd party applications, including LTI. --> + <!-- Uncomment the line below to support LTI or other OAuth clients. --> + <ref bean="oauthProtectedResourceFilter" /> + </list> + </property> + </bean> + + <!-- ################################ --> + <!-- # Pre-Aauthentication Filters # --> + <!-- ################################ --> + + <bean id="preAuthenticationFilters" class="org.springframework.web.filter.CompositeFilter"> + <property name="filters"> + <list> + <!-- Uncomment the line below to support Shibboleth. --> + <!-- <ref bean="shibbolethHeaderFilter" /> --> + + <!-- Uncomment the line below to support JWT. + <ref bean="jwtHeaderFilter" /> --> + <!-- Additionally/alternatively uncomment this to support passing a JWT in a URL parameter. + <ref bean="jwtRequestParameterFilter" /> --> + </list> + </property> + </bean> + + <!-- ########################################### --> + <!-- # Custom ajax timeout Filter Definition # --> + <!-- ########################################### --> + + <bean id="asyncTimeoutRedirectFilter" class="org.opencastproject.kernel.security.AsyncTimeoutRedirectFilter" /> + + <!-- ######################################## --> + <!-- # Custom Anonymous Filter Definition # --> + <!-- ######################################## --> + + <bean id="anonymousFilter" class="org.opencastproject.kernel.security.TrustedAnonymousAuthenticationFilter"> + <property name="userAttribute" ref="anonymousUserAttributes" /> + <property name="key" value="anonymousKey" /> + </bean> + + <bean id="anonymousUserAttributes" class="org.springframework.security.core.userdetails.memory.UserAttribute"> + <property name="authoritiesAsString" value="ROLE_ANONYMOUS"/> + <property name="password" value="empty"/> + </bean> + + <!-- ######################################## --> + <!-- # Authentication Entry and Exit Points # --> + <!-- ######################################## --> + + <!-- Differentiates between "normal" user requests and those requesting digest auth --> + <bean id="opencastEntryPoint" class="org.opencastproject.kernel.security.DelegatingAuthenticationEntryPoint"> + <!-- CAS Auth: Comment this if using CAS authentication --> + <property name="userEntryPoint" ref="userEntryPoint" /> + <!-- CAS Auth: Uncomment this if using CAS authentication --> + <!-- <property name="userEntryPoint" ref="casEntryPoint" /> --> + <property name="digestAuthenticationEntryPoint" ref="digestEntryPoint" /> + <property name="basicAuthenticationEntryPoint" ref="basicEntryPoint" /> + </bean> + + <!-- Redirects unauthenticated requests to the login form --> + <bean id="userEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> + <property name="loginFormUrl" value="/login.html" /> + </bean> + + <!-- Redirect unauthenticated requests to custom login url with configurable redirect query parameter + Example: http://localhost/Shibboleth.sso/Login?target=<RELATIVE_REQUEST_URL> --> + <!--bean id="userEntryPoint" class="org.opencastproject.kernel.security.RedirectQueryParamAuthenticationEntryPoint"> + <constructor-arg index="0" value="/Shibboleth.sso/Login" /> + <constructor-arg index="1" value="target" /> + </bean--> + + <!-- Returns a 401 request for authentication via digest auth --> + <bean id="digestEntryPoint" class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint"> + <property name="realmName" value="Opencast" /> + <property name="key" value="opencast" /> + <property name="nonceValiditySeconds" value="300" /> + </bean> + + <bean id="basicEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> + <property name="realmName" value="Opencast"/> + </bean> + + <bean id="authSuccessHandler" class="org.opencastproject.kernel.security.AuthenticationSuccessHandler"> + <property name="securityService" ref="securityService" /> + <property name="welcomePages"> + <map> + <entry key="ROLE_ADMIN" value="/index.html" /> + <entry key="ROLE_ADMIN_UI" value="/index.html" /> + <entry key="*" value="/engage/ui/index.html" /> <!-- Any role not listed explicitly will redirect here --> + </map> + </property> + </bean> + + <bean id="logoutSuccessHandler" class="org.opencastproject.kernel.security.LogoutSuccessHandler"> + <property name="userDirectoryService" ref="userDirectoryService" /> + <!-- Shibboleth log out + <property name="defaultTargetUrl" value="/Shibboleth.sso/Logout?return=www.opencast.org"/> + --> + </bean> + + <!-- ################# --> + <!-- # Digest Filter # --> + <!-- ################# --> + + <!-- Handles the details of the digest authentication dance --> + <bean id="digestFilter" class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter"> + <!-- Use only the in-memory users, as these have passwords that are not hashed --> + <property name="userDetailsService" ref="userDetailsService" /> + <property name="authenticationEntryPoint" ref="digestEntryPoint" /> + <property name="createAuthenticatedToken" value="true" /> + <property name="userCache"> + <bean class="org.springframework.security.core.userdetails.cache.NullUserCache" /> + </property> + </bean> + + <!-- ############################### --> + <!-- # Basic Authentication Filter # --> + <!-- ############################### --> + + <!-- Handles the details of the basic authentication dance --> + <bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> + <property name="authenticationManager" ref="authenticationManager"/> + <property name="authenticationEntryPoint" ref="basicEntryPoint"/> + </bean> + + <!-- ####################### --> + <!-- # OAuth (LTI) Support # --> + <!-- ####################### --> + + <bean name="oauthProtectedResourceFilter" class="org.opencastproject.kernel.security.LtiProcessingFilter"> + <property name="consumerDetailsService" ref="oAuthConsumerDetailsService" /> + <property name="tokenServices"> + <bean class="org.springframework.security.oauth.provider.token.InMemoryProviderTokenServices" /> + </property> + <property name="nonceServices"> + <bean class="org.springframework.security.oauth.provider.nonce.InMemoryNonceServices" /> + </property> + <property name="authHandler" ref="ltiLaunchAuthenticationHandler" /> + </bean> + + <!-- ############### --> + <!-- # CAS Support # --> + <!-- ############### --> + <!-- + <bean id="casFilter" + class="org.springframework.security.cas.web.CasAuthenticationFilter"> + <property name="authenticationManager" ref="authenticationManager"/> + <property name="authenticationSuccessHandler" ref="authSuccessHandler" /> + <property name="serviceProperties" ref="serviceProperties" /> + <property name="authenticationDetailsSource"> + <bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/> + </property> + </bean> + + <bean id="casEntryPoint" + class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> + <property name="loginUrl" value="https://auth-test.berkeley.edu/cas/login"/> + <property name="serviceProperties" ref="serviceProperties"/> + </bean> + + <bean id="serviceProperties" + class="org.springframework.security.cas.ServiceProperties"> + <property name="service" value="https://localhost/j_spring_cas_security_check"/> + <property name="sendRenew" value="false"/> + </bean> + + <bean id="casAuthenticationProvider" + class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> + <property name="serviceProperties" ref="serviceProperties" /> + <property name="authenticationUserDetailsService"> + <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> + <constructor-arg ref="userDetailsService" /> + </bean> + </property> + <property name="ticketValidator"> + <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> + <constructor-arg index="0" value="https://auth-test.berkeley.edu/cas" /> + </bean> + </property> + <property name="key" value="cas"/> + </bean> + + <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> + + <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> + <constructor-arg value="https://auth-test.berkeley.edu/cas/logout"/> + <constructor-arg> + <bean class= "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> + </constructor-arg> + <property name="filterProcessesUrl" value="https://localhost/j_spring_security_logout"/> + </bean> + --> + + <!-- ###################### --> + <!-- # Shibboleth Support # --> + <!-- ###################### --> + + <!-- General Shibboleth header extration filter + <bean id="shibbolethHeaderFilter" + class="org.opencastproject.security.shibboleth.ShibbolethRequestHeaderAuthenticationFilter"> + <property name="principalRequestHeader" value="<this need to be configured>"/> + <property name="authenticationManager" ref="authenticationManager" /> + <property name="userDetailsService" ref="userDetailsService" /> + <property name="userDirectoryService" ref="userDirectoryService" /> + <property name="shibbolethLoginHandler" ref="aaiLoginHandler" /> + <property name="exceptionIfHeaderMissing" value="false" /> + </bean>--> + + <!-- AAI header extractor and user generator + <bean id="aaiLoginHandler" class="org.opencastproject.security.aai.ConfigurableLoginHandler"> + <property name="securityService" ref="securityService" /> + <property name="userReferenceProvider" ref="userReferenceProvider" /> + </bean>--> + + <!-- Dynamic AAI Loginhandler + <bean id="aaiLoginHandler" class="org.opencastproject.security.aai.DynamicLoginHandler"> + <property name="securityService" ref="securityService" /> + <property name="userReferenceProvider" ref="userReferenceProvider" /> + <property name="attributeMapper" ref="attributeMapper" /> + </bean> + + <bean id="attributeMapper" class="org.opencastproject.security.aai.api.AttributeMapper"> + <property name="useHeader" value="true" /> + <property name="multiValueDelimiter" value=";" /> + <property name="attributeMap" ref="attributeMap" /> + <property name="aaiAttributes" ref="aaiAttributes" /> + </bean> + + <util:list id="aaiAttributes" value-type="java.lang.String"> + <value>sn</value> + <value>givenName</value> + <value>mail</value> + <value>homeOrganization</value> + <value>eduPersonEntitlement</value> + <value>eduPersonPrincipalName</value> + <value>homeOrganization</value> + </util:list> + + <util:map id="attributeMap" map-class="java.util.HashMap"> + <entry key="roles" value-ref="roleMapping" /> + <entry key="displayName" value-ref="displayNameMapping" /> + <entry key="mail" value-ref="mailMapping" /> + </util:map> + + <util:list id="displayNameMapping" value-type="java.lang.String"> + <value>['givenName'][0] + ' ' + ['sn'][0]</value> + </util:list> + + <util:list id="mailMapping" value-type="java.lang.String"> + <value>['mail'][0]</value> + </util:list> + + <util:list id="roleMapping" value-type="java.lang.String"> + <value>'ROLE_AAI_USER'</value> + <value>'ROLE_AAI_USER_' + ['eduPersonPrincipalName']</value> + <value>('ROLE_AAI_OWNER_' + ['eduPersonPrincipalName']).replaceAll("[^a-zA-Z0-9]","_").toUpperCase()</value> + <value>['homeOrganization'] != null ? 'ROLE_AAI_ORG_' + ['homeOrganization'] + '_MEMBER' : null</value> + <value>['eduPersonEntitlement'].contains('urn:mace:opencast.org:permission:shibboleth:opencast_admin') ? 'ROLE_ADMIN' : null</value> + <value>['eduPersonPrincipalName'].contains('john.doe@example.org') ? 'ROLE_ADMIN' : null</value> + <value>['eduPersonScopedAffiliation'].contains('faculty@example.org') ? 'ROLE_GROUP_AAI_TRAINER' : null</value> + </util:list> + + --> + + <bean id="preauthAuthProvider" + class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> + <property name="preAuthenticatedUserDetailsService"> + <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> + <property name="userDetailsService" ref="userDetailsService"/> + </bean> + </property> + </bean> + + <!-- ################ --> + <!-- # JWT Support # --> + <!-- ################ --> + + <!-- General JWT header extraction filter + <bean id="jwtHeaderFilter" class="org.opencastproject.security.jwt.JWTRequestHeaderAuthenticationFilter"> + <property name="principalRequestHeader" value="Authorization"/> + <property name="principalPrefix" value="Bearer "/> + <property name="authenticationManager" ref="authenticationManager" /> + <property name="loginHandler" ref="jwtLoginHandler" /> + <property name="exceptionIfHeaderMissing" value="false" /> + </bean>--> + + <!-- General JWT request parameter extraction filter + <bean id="jwtRequestParameterFilter" class="org.opencastproject.security.jwt.JWTRequestParameterAuthenticationFilter"> + <property name="parameterName" value="jwt" /> + <property name="authenticationManager" ref="authenticationManager" /> + <property name="loginHandler" ref="jwtLoginHandler" /> + <property name="exceptionIfParameterMissing" value="false" /> + </bean>--> + + <!-- JWT login handler + <bean id="jwtLoginHandler" class="org.opencastproject.security.jwt.DynamicLoginHandler"> + <property name="userDetailsService" ref="userDetailsService" /> + <property name="userDirectoryService" ref="userDirectoryService" /> + <property name="securityService" ref="securityService" /> + <property name="userReferenceProvider" ref="userReferenceProvider" /> + <property name="jwksUrl" value="https://auth.example.org/.well-known/jwks.json" /> + <property name="jwksCacheExpiresIn" value="1440" /> + <property name="secret" value="***" /> + <property name="expectedAlgorithms" ref="jwtExpectedAlgorithms" /> + <property name="claimConstraints" ref="jwtClaimConstraints" /> + <property name="usernameMapping" value="['username'].asString()" /> + <property name="nameMapping" value="['name'].asString()" /> + <property name="emailMapping" value="['email'].asString()" /> + <property name="roleMappings" ref="jwtRoleMappings" /> + <property name="jwtCacheSize" value="500" /> + <property name="jwtCacheExpiresIn" value="60" /> + </bean>--> + + <!-- The signing algorithms expected for the JWT signature + <util:list id="jwtExpectedAlgorithms" value-type="java.lang.String"> + <value>RS256</value> + </util:list>--> + + <!-- The claim constraints that are expected to be fulfilled by the JWT + If you are using JWTs for OpenID Connect, see the specification for claims that must be validated: + https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + <util:list id="jwtClaimConstraints" value-type="java.lang.String"> + <value>containsKey('iss')</value> + <value>containsKey('aud')</value> + <value>containsKey('username')</value> + <value>containsKey('name')</value> + <value>containsKey('email')</value> + <value>containsKey('domain')</value> + <value>containsKey('affiliation')</value> + <value>['iss'].asString() eq 'https://auth.example.org'</value> + <value>['aud'].asString() eq 'client-id'</value> + <value>['username'].asString() matches '.*@example\.org'</value> + <value>['domain'].asString() eq 'example.org'</value> + <value>['affiliation'].asList(T(String)).contains('faculty@example.org')</value> + </util:list>--> + + <!-- The mapping from JWT claims to Opencast roles + <util:list id="jwtRoleMappings" value-type="java.lang.String"> + <value>'ROLE_JWT_USER'</value> + <value>'ROLE_JWT_USER_' + ['username'].asString()</value> + <value>('ROLE_JWT_OWNER_' + ['username'].asString()).replaceAll("[^a-zA-Z0-9]","_").toUpperCase()</value> + <value>['domain'] != null ? 'ROLE_JWT_ORG_' + ['domain'].asString() + '_MEMBER' : null</value> + <value>['username'].asString() eq ('j_doe01@example.org') ? 'ROLE_ADMIN' : null</value> + <value>['affiliation'].asList(T(String)).contains('faculty@example.org') ? 'ROLE_GROUP_JWT_TRAINER' : null</value> + </util:list>--> + + <!-- ################ --> + <!-- # LDAP Support # --> + <!-- ################ --> + + <!-- + <bean id="contextSource" + class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> + < ! - - URL of the LDAP server - - > + <constructor-arg value="ldap://myldapserver:myport" /> + < ! - - "Distinguished name" for the unprivileged user - - > + < ! - - This user is merely to perform searches in the LDAP to find the users to login - - > + <property name="userDn" value="uid=user-id,dc=example,dc=com" /> + < ! - - Password of the user above - - > + <property name="password" value="mypassword" /> + </bean> + --> + + <!-- + <bean id="userDetailsMapper" class="org.opencastproject.security.ldap.OpencastUserDetailsMapper"> + <constructor-arg ref="userDetailsService" /> + </bean> + --> + + <!-- + <bean id="ldapAuthProvider" + class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> + <constructor-arg> + <bean + class="org.springframework.security.ldap.authentication.BindAuthenticator"> + <constructor-arg ref="contextSource" /> + <property name="userDnPatterns"> + <list> + < ! - - Dn patterns to search for valid users. Multiple "<value>" tags are allowed - - > + <value>uid={0},dc=example,dc=com</value> + </list> + </property> + < ! - - If your user IDs are not part of the user Dn's, you can use a search filter to find them - - > + < ! - - This property can be used together with the "userDnPatterns" above - - > + < ! - - + <property name="userSearch"> + <bean name="filterUserSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> + < ! - - Base Dn from where the users will be searched for - - > + <constructor-arg index="0" value="ou=GroupName,dc=my-institution,dc=country" /> + < ! - - Filter to located valid users. Use {0} as a placeholder for the login name - - > + <constructor-arg index="1" value="(uid={0})" /> + <constructor-arg ref="contextSource" /> + </bean> + </property> + - - > + </bean> + </constructor-arg> + < ! - - Retrieve user and attributes from opencasts user details service - - > + <property name="userDetailsContextMapper" ref="userDetailsMapper"/> + < ! - - Defines how the user attributes are converted to authorities (roles) - - > + < ! - - Output is ignored if used together with userDetailsMapper - - > + < ! - - constructor-arg ref="authoritiesPopulator" /- - > + </bean> + --> + + <!-- #################### --> + <!-- # OSGI Integration # --> + <!-- #################### --> + + <!-- Obtain services from the OSGI service registry --> + <osgi:reference id="userDetailsService" cardinality="1..1" + interface="org.springframework.security.core.userdetails.UserDetailsService" /> + + <osgi:reference id="securityService" cardinality="1..1" + interface="org.opencastproject.security.api.SecurityService" /> + + <!-- Uncomment to enable external users e.g. used together with shibboleth and JWT --> + <!-- <osgi:reference id="userReferenceProvider" cardinality="1..1" + interface="org.opencastproject.userdirectory.api.UserReferenceProvider" /> --> + + <osgi:reference id="userDirectoryService" cardinality="1..1" + interface="org.opencastproject.security.api.UserDirectoryService" /> + + <!-- Uncomment this as an alternative to the userDetailsMapper --> + <!-- Make sure you provide the same instanceId you used in org.opencastproject.userdirectory.ldap-….cfg --> + <!-- + <osgi:reference id="authoritiesPopulator" cardinality="1..1" + interface="org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator" + filter="(instanceId=theId)"/> + --> + + <osgi:reference id="oAuthConsumerDetailsService" cardinality="1..1" + interface="org.springframework.security.oauth.provider.ConsumerDetailsService" /> + + <osgi:reference id="ltiLaunchAuthenticationHandler" cardinality="1..1" + interface="org.springframework.security.oauth.provider.OAuthAuthenticationHandler" /> + + + <!-- ############################# --> + <!-- # Spring Security Internals # --> + <!-- ############################# --> + + <bean id="passwordEncoder" class="org.opencastproject.kernel.security.CustomPasswordEncoder" /> + + <sec:authentication-manager alias="authenticationManager"> + <sec:authentication-provider ref="rememberMeAuthenticationProvider"/> + <!-- CAS Auth: Uncomment this if using CAS authentication --> + <!-- <sec:authentication-provider ref="casAuthenticationProvider" /> --> + <!-- Uncomment this if using Shibboleth or JWT authentication --> + <!-- <sec:authentication-provider ref="preauthAuthProvider" /> --> + <!-- Uncomment the following line if using LDAP --> + <!-- <sec:authentication-provider ref="ldapAuthProvider" /> --> + <sec:authentication-provider user-service-ref="userDetailsService"> + <!-- The JPA user directory stores bcrypt hashed passwords, but still works with legacy md5 hashes --> + <sec:password-encoder ref="passwordEncoder"> + <!-- This salt is used only for decoding legacy MD5 hased passwords --> + <sec:salt-source user-property="username" /> + </sec:password-encoder> + </sec:authentication-provider> + </sec:authentication-manager> + + <!-- Do not use a request cache --> + <bean id="requestCache" class="org.springframework.security.web.savedrequest.NullRequestCache" /> + +</beans> diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000000000000000000000000000000000000..6093ca043e3b076211663a040f7866a953c4e37e --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,67 @@ +name: Run plugin tests +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP with Composer + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer:v2 + + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Build Opencast plugin + run: npm run build-dev + + - name: Start containers + working-directory: ./.github/docker + run: docker compose up --wait + + # Needed as studip directory has root owner and chown on whole directory takes too long + - name: Allow read, write and execute for all users in data and plugin directory + working-directory: ./.github/docker + run: docker compose exec studip chmod -R 777 data public/plugins_packages + + # Auto migrate in studip image seems to be broken + - name: Migrate studip + working-directory: ./.github/docker + run: docker compose exec studip php ./cli/studip migrate + + - name: Register plugin + working-directory: ./.github/docker + run: docker compose exec studip php ./cli/studip plugin:register public/plugins_packages/elan-ev/OpencastV3 + + - name: Activate plugin + working-directory: ./.github/docker + run: docker compose exec studip php ./cli/studip plugin:activate OpencastV3 + + - name: Configure plugin + working-directory: ./.github/docker + run: docker compose exec -T studip_db mysql -u studip_user --password=studip_password studip_db < ./oc.sql + + - name: Trigger playlists migration to Opencast + run: curl http://localhost/plugins.php/opencastv3/api/migrate_playlists -u root@studip:testing + + - name: Run tests + run: npm run tests + + - name: Stop containers + working-directory: ./.github/docker + if: always() + run: docker compose down diff --git a/codeception.yml b/codeception.yml index 3cec6442d169a4f79354f476c36190b4e32e98c3..b709ae2860f7c46b1f3fc5a298985e07839e84fe 100644 --- a/codeception.yml +++ b/codeception.yml @@ -6,8 +6,20 @@ suites: modules: enabled: - REST: - url: https://studip.me/testip/plugins.php/opencast/api + url: '%STUDIP_REST_URL%' depends: PhpBrowser + - \Helper\Api: + opencast_rest_url: '%OPENCAST_REST_URL%' + api_token: '%API_TOKEN%' + opencast_admin_user: '%OPENCAST_ADMIN_NAME%' + opencast_admin_password: '%OPENCAST_ADMIN_PASSWORD%' + dozent_name: '%DOZENT_NAME%' + dozent_password: '%DOZENT_PASSWORD%' + course_student: '%COURSE_STUDENT%' + author_name: '%AUTHOR_NAME%' + author_password: '%AUTHOR_PASSWORD%' + config_id: '%CONFIG_ID%' + course_id: '%COURSE_ID%' step_decorators: - \Codeception\Step\AsJson @@ -19,4 +31,8 @@ paths: settings: shuffle: false - lint: true \ No newline at end of file + lint: true + +params: + - env + - tests/.env diff --git a/composer.json b/composer.json index 29139afc86939f35c322578b1cab6224a8f62fae..8d40b202dd4a875160dd206fdb11aacc1bae57ee 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "codeception/codeception": "^4.2", "codeception/module-phpbrowser": "^1.0.0", "codeception/module-asserts": "^1.0.0", - "codeception/module-rest": "^1.0.0" + "codeception/module-rest": "^1.0.0", + "vlucas/phpdotenv": "^5.6" } } diff --git a/composer.lock b/composer.lock index c4fec537b4ca24c2f4d12f04490582d847f4f85a..85f64fc6bfdf0b6fd802cd32cdf6a412d023325c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa92a4107e50c23dc29961cd10970822", + "content-hash": "24cbffd2270b78d8e4431b02761bc4f3", "packages": [ { "name": "elan-ev/opencast-api", @@ -1261,6 +1261,68 @@ ], "time": "2022-12-30T00:15:36+00:00" }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, { "name": "justinrainbow/json-schema", "version": "v5.2.13", @@ -1509,6 +1571,81 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "7.0.17", @@ -4105,6 +4242,90 @@ } ], "time": "2024-03-03T12:36:25+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:52:34+00:00" } ], "aliases": [], @@ -4117,5 +4338,5 @@ "platform-overrides": { "php": "7.2.5" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/courseware/devbuild.sh b/courseware/devbuild.sh new file mode 100644 index 0000000000000000000000000000000000000000..9364c4968b52fd6c6b68ab0ca7dc62be81969b8a --- /dev/null +++ b/courseware/devbuild.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +rm -rf ./node_modules +npm run build-dev \ No newline at end of file diff --git a/courseware/package.json b/courseware/package.json index 9c75ad4e15eb42cd26b2b60028365d32e22a76ec..69e3208625e657460fc17a57c6a27d415b2b1609 100644 --- a/courseware/package.json +++ b/courseware/package.json @@ -23,6 +23,7 @@ "scripts": { "prebuild": "npm install", "build": "webpack --mode production --config ./webpack-courseware.config.js", + "build-dev": "webpack --mode development --config ./webpack-courseware.config.js", "devcw": "webpack --mode development --watch --config ./webpack-courseware.config.js", "dev": "webpack --mode development --watch --config ./webpack-courseware.config.js" }, diff --git a/lib/Models/PlaylistSeminars.php b/lib/Models/PlaylistSeminars.php index 28e2abcc87a74f1a71c8333c42cbdca9c19c5484..f567888773cb067924638b75dd19c8ae054bac10 100644 --- a/lib/Models/PlaylistSeminars.php +++ b/lib/Models/PlaylistSeminars.php @@ -219,7 +219,7 @@ class PlaylistSeminars extends \SimpleORMap $course = \Course::find($course_id); // Check if user has access to this seminar - if ($perm->have_studip_perm($course_id, 'user', $user_id)) { + if ($perm->have_studip_perm('user', $course_id, $user_id)) { $lecturers = []; $lecturers_obj = $course->getMembersWithStatus('dozent'); foreach ($lecturers_obj as $lecturer) { diff --git a/lib/Models/Playlists.php b/lib/Models/Playlists.php index e53501a7c6423b14332d6d9df3f760304b370897..2fa6fed74345c9c0d1515b56b995e6e92514e238 100644 --- a/lib/Models/Playlists.php +++ b/lib/Models/Playlists.php @@ -195,7 +195,7 @@ class Playlists extends UPMap $course = \Course::find($filter['value']); // check, if user has access to this seminar - if (!empty($course) && $perm->have_studip_perm($course->id, 'user')) { + if (!empty($course) && $perm->have_studip_perm('user', $course->id)) { $courses[$course->id] = [ 'id' => $course->id, 'compare' => $filter['compare'] diff --git a/lib/Models/Videos.php b/lib/Models/Videos.php index 1422f7fee251f6c21a7defaa929eabad17871ba2..87b899aa218d113902f9b41cc48dfe62390075d3 100644 --- a/lib/Models/Videos.php +++ b/lib/Models/Videos.php @@ -281,7 +281,7 @@ class Videos extends UPMap $course = \Course::find($filter['value']); // check, if user has access to this seminar - if (!empty($course) && $perm->have_studip_perm($course->id, 'user')) { + if (!empty($course) && $perm->have_studip_perm('user', $course->id)) { $course_ids[$course->id] = [ 'id' => $course->id, 'compare' => $filter['compare'] diff --git a/lib/Routes/Course/CourseListForPlaylistVideos.php b/lib/Routes/Course/CourseListForPlaylistVideos.php index 60d7dbd16dabf0d70e136cfc4ef90e7808648f7d..b68bde294577b9ddd55be9bba3f1cd6e3d50ce8a 100644 --- a/lib/Routes/Course/CourseListForPlaylistVideos.php +++ b/lib/Routes/Course/CourseListForPlaylistVideos.php @@ -33,7 +33,7 @@ class CourseListForPlaylistVideos extends OpencastController // check if playlist is connected to the passed course and user is part of that course as well $permission = false; if ($params['cid']) { - if ($perm->have_studip_perm($params['cid'], 'user')) { + if ($perm->have_studip_perm('user', $params['cid'])) { $permission = true; } } diff --git a/lib/Routes/Course/CourseListPlaylist.php b/lib/Routes/Course/CourseListPlaylist.php index a605e3c8baa93abf36752e339e6d3d62181a9896..1708d0d439ba492caebd34f621a07af5601595e8 100644 --- a/lib/Routes/Course/CourseListPlaylist.php +++ b/lib/Routes/Course/CourseListPlaylist.php @@ -33,7 +33,7 @@ class CourseListPlaylist extends OpencastController } // check if user has access to this seminar - if (!$perm->have_studip_perm($course_id, 'user')) { + if (!$perm->have_studip_perm('user', $course_id)) { throw new \AccessDeniedException(); } diff --git a/lib/Routes/Playlist/PlaylistVideoList.php b/lib/Routes/Playlist/PlaylistVideoList.php index cb578e25656d91e8ec5ce0568264d27d7ce613ea..64b7b1d38602670d627dc093d6b1ed6f4f31ee33 100644 --- a/lib/Routes/Playlist/PlaylistVideoList.php +++ b/lib/Routes/Playlist/PlaylistVideoList.php @@ -33,7 +33,7 @@ class PlaylistVideoList extends OpencastController // check if playlist is connected to the passed course and user is part of that course as well $permission = false; if ($params['cid']) { - if ($perm->have_studip_perm($params['cid'], 'user')) { + if ($perm->have_studip_perm('user', $params['cid'])) { $permission = true; } } diff --git a/lib/Routes/Tags/TagListForPlaylistVideos.php b/lib/Routes/Tags/TagListForPlaylistVideos.php index b21790f2a74708f7326a70c5988016c299b6296f..763fb082df20911008d7083299e0083949c77d6f 100644 --- a/lib/Routes/Tags/TagListForPlaylistVideos.php +++ b/lib/Routes/Tags/TagListForPlaylistVideos.php @@ -30,7 +30,7 @@ class TagListForPlaylistVideos extends OpencastController // check if playlist is connected to the passed course and user is part of that course as well $permission = false; if ($params['cid']) { - if ($perm->have_studip_perm($params['cid'], 'user')) { + if ($perm->have_studip_perm('user', $params['cid'])) { $permission = true; } } diff --git a/lib/Routes/Video/VideoAdd.php b/lib/Routes/Video/VideoAdd.php index 9133e309fd1c6cd678de771012eb809d4ee2140c..d3fb4a707f6bcdbbc8e1ef8fd5d62e75e3ce1c80 100644 --- a/lib/Routes/Video/VideoAdd.php +++ b/lib/Routes/Video/VideoAdd.php @@ -68,7 +68,7 @@ class VideoAdd extends OpencastController } // Assign permissions to teachers of the course, when it is a student upload in a course. - if (!empty($course_id) && $perm->have_studip_perm($course_id, 'user', $user->id)) { + if (!empty($course_id) && $perm->have_studip_perm('user', $course_id, $user->id)) { VideosUserPerms::assignCourseLecturerPermissions($course_id, $video->id); } diff --git a/package.json b/package.json index b26c6d104d932b1eb93c86ae5c2fb2cf5f66a2c3..63e33a7abc4ce2f888a5600c3b0dfcf97b4df8b1 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "author": "Till Glöggler <tgloeggl@uos.de>", "scripts": { "build": "composer install --no-dev && npm install && webpack --mode production -d source-map && cd courseware; sh prebuild.sh", + "build-dev": "composer install && npm install && webpack --mode development -d source-map && cd courseware; sh devbuild.sh", "devcw": "cd courseware; sh watch.sh", "predev": "npm install", "dev": "webpack --mode development --watch", diff --git a/tests/.env b/tests/.env new file mode 100644 index 0000000000000000000000000000000000000000..16d49e7604b14b139f948dfbd5b850059bb71a10 --- /dev/null +++ b/tests/.env @@ -0,0 +1,27 @@ +STUDIP_REST_URL=http://localhost/plugins.php/opencastv3/api +OPENCAST_REST_URL=http://127.0.0.1:8081/api + +# Opencast server ID +CONFIG_ID=1 + +# Opencast API token for user provider +API_TOKEN=mytoken1234abcdef + +# Opencast admin user +OPENCAST_ADMIN_NAME=admin +OPENCAST_ADMIN_PASSWORD=opencast + + +# Stud.IP dozent in test course and +DOZENT_NAME=test_dozent +DOZENT_PASSWORD=testing + +# Student in the test course +COURSE_STUDENT=test_autor + +# Simple author in Stud.IP whithout course subscriptions +AUTHOR_NAME=simple_autor +AUTHOR_PASSWORD=testing + +# ID of test course +COURSE_ID=a07535cf2f8a72df33c12ddfa4b53dde diff --git a/tests/AclCest.php b/tests/AclCest.php new file mode 100644 index 0000000000000000000000000000000000000000..faa0ace3a34dbb0957c0c07e21f1461a41aa101f --- /dev/null +++ b/tests/AclCest.php @@ -0,0 +1,159 @@ +<?php + +class AclCest +{ + private $opencast_rest_url; + private $config_id; + private $api_token; + private $opencast_admin_user; + private $opencast_admin_password; + private $dozent_name; + private $course_student; + private $course_id; + + public function _before(ApiTester $I) + { + $config = $I->getConfig(); + + $this->opencast_rest_url = $config['opencast_rest_url']; + $this->config_id = $config['config_id']; + $this->api_token = $config['api_token']; + $this->opencast_admin_user = $config['opencast_admin_user']; + $this->opencast_admin_password = $config['opencast_admin_password']; + $this->dozent_name = $config['dozent_name']; + $this->course_student = $config['course_student']; + $this->course_id = $config['course_id']; + + $I->amHttpAuthenticated($config['dozent_name'], $config['dozent_password']); + } + + // tests + public function testPlaylistAcl(ApiTester $I) + { + $playlist = [ + 'title' => 'Meine Videos' , + 'description' => 'Videoliste', + 'visibility' => 'internal', + 'config_id' => $this->config_id, + ]; + + $response = $I->sendPostAsJson('/playlists', $playlist); + + $I->seeResponseCodeIs(201); + $I->seeResponseIsJson(); + + $I->seeResponseContainsJson($playlist); + $I->seeResponseContainsJson(['users' => [['perm' => 'owner']]]); + + list($service_playlist_id) = $I->grabDataFromResponseByJsonPath('$.service_playlist_id'); + + // Check if user has correct playlist role + $response = $I->sendGetAsJson('/opencast/user/' . $this->dozent_name, ['token' => $this->api_token]); + + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + + $I->seeResponseContainsJson([ + 'username' => $this->dozent_name, + 'roles' => [ + 'PLAYLIST_' . $service_playlist_id . '_write', + ] + ]); + + // Check ACLs in Opencast + + // Login as opencast admin + $I->amHttpAuthenticated($this->opencast_admin_user, $this->opencast_admin_password); + + $response = $I->sendGetAsJson($this->opencast_rest_url . '/playlists/' . $service_playlist_id); + $I->seeResponseContainsJson(['accessControlEntries' => [ + ['allow' => true, 'role' => 'PLAYLIST_' . $service_playlist_id . '_read', 'action' => 'read'], + ['allow' => true, 'role' => 'PLAYLIST_' . $service_playlist_id . '_write', 'action' => 'read'], + ['allow' => true, 'role' => 'PLAYLIST_' . $service_playlist_id . '_write', 'action' => 'write'], + ]]); + } + + public function testCoursePlaylistAcl(ApiTester $I) + { + // Create a playlist + $playlist = [ + 'title' => 'Meine Videos' , + 'description' => 'Videoliste', + 'visibility' => 'internal', + 'config_id' => $this->config_id, + ]; + + $response = $I->sendPostAsJson('/playlists', $playlist); + $I->seeResponseCodeIs(201); + $I->seeResponseIsJson(); + + $I->seeResponseContainsJson($playlist); + $I->seeResponseContainsJson(['users' => [['perm' => 'owner']]]); + + list($token) = $I->grabDataFromResponseByJsonPath('$.token'); + list($service_playlist_id) = $I->grabDataFromResponseByJsonPath('$.service_playlist_id'); + + // Add playlist to course + $response = $I->sendPost('/courses/' . $this->course_id . '/playlist/' . $token); + $I->seeResponseCodeIs(204); + + // Check if student of course has read access only + $response = $I->sendGetAsJson('/opencast/user/' . $this->course_student, ['token' => $this->api_token]); + + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + + $I->seeResponseContainsJson([ + 'username' => $this->course_student, + 'roles' => [ + 'PLAYLIST_' . $service_playlist_id . '_read', + ] + ]); + $I->dontSeeResponseContainsJson(['roles' => [ + 'PLAYLIST_' . $service_playlist_id . '_write', + ]]); + } + + public function testRemoveCoursePlaylistAcl(ApiTester $I) + { + // Create a playlist + $playlist = [ + 'title' => 'Meine Videos' , + 'description' => 'Videoliste', + 'visibility' => 'internal', + 'config_id' => $this->config_id, + ]; + + $response = $I->sendPostAsJson('/playlists', $playlist); + $I->seeResponseCodeIs(201); + $I->seeResponseIsJson(); + + $I->seeResponseContainsJson($playlist); + $I->seeResponseContainsJson(['users' => [['perm' => 'owner']]]); + + list($token) = $I->grabDataFromResponseByJsonPath('$.token'); + list($service_playlist_id) = $I->grabDataFromResponseByJsonPath('$.service_playlist_id'); + + // Add playlist to course + $response = $I->sendPost('/courses/' . $this->course_id . '/playlist/' . $token); + $I->seeResponseCodeIs(204); + + // Remove playlist from course + $response = $I->sendDelete('/courses/' . $this->course_id . '/playlist/' . $token); + $I->seeResponseCodeIs(204); + + // Check if student of course has no access + $response = $I->sendGetAsJson('/opencast/user/' . $this->course_student, ['token' => $this->api_token]); + + $I->seeResponseCodeIs(200); + $I->seeResponseIsJson(); + + $I->dontseeResponseContainsJson([ + 'username' => $this->course_student, + 'roles' => [ + 'PLAYLIST_' . $service_playlist_id . '_read', + 'PLAYLIST_' . $service_playlist_id . '_write', + ] + ]); + } +} diff --git a/tests/CoursesCest.php b/tests/CoursesCest.php index 9376dc5d334d328a74925b735d65e5a849403f59..2e257081eb07df0ffffb54d19316b1b88ea66b31 100644 --- a/tests/CoursesCest.php +++ b/tests/CoursesCest.php @@ -2,11 +2,17 @@ class CoursesCest { - private $course_id = 'a07535cf2f8a72df33c12ddfa4b53dde'; + private $config_id; + private $course_id; public function _before(ApiTester $I) { - $I->amHttpAuthenticated('apitester', 'apitester'); + $config = $I->getConfig(); + + $this->config_id = $config['config_id']; + $this->course_id = $config['course_id']; + + $I->amHttpAuthenticated($config['dozent_name'], $config['dozent_password']); } // tests @@ -16,7 +22,8 @@ class CoursesCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -29,13 +36,18 @@ class CoursesCest list($token) = $I->grabDataFromResponseByJsonPath('$.token'); // Add playlist to course - $response = $I->sendPut('/courses/' . $this->course_id . '/playlist/' . $token); + $response = $I->sendPost('/courses/' . $this->course_id . '/playlist/' . $token); $I->seeResponseCodeIs(204); - $response = $I->sendGet('/courses/' . $this->course_id . '/playlist'); + $response = $I->sendGet('/courses/' . $this->course_id . '/playlists'); $I->seeResponseCodeIs(200); $I->seeResponseIsJson(); - $I->seeResponseContainsJson($playlist); + $I->seeResponseContainsJson([ + 'title' => $playlist['title'], + 'description' => $playlist['description'], + 'visibility' => 'visible', + 'config_id' => $playlist['config_id'], + ]); } public function testAddPlaylist(ApiTester $I) @@ -44,7 +56,8 @@ class CoursesCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -57,7 +70,7 @@ class CoursesCest list($token) = $I->grabDataFromResponseByJsonPath('$.token'); // Add playlist to course - $response = $I->sendPut('/courses/' . $this->course_id . '/playlist/' . $token); + $response = $I->sendPost('/courses/' . $this->course_id . '/playlist/' . $token); $I->seeResponseCodeIs(204); } @@ -67,7 +80,8 @@ class CoursesCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -80,7 +94,7 @@ class CoursesCest list($token) = $I->grabDataFromResponseByJsonPath('$.token'); // Add playlist to course - $response = $I->sendPut('/courses/' . $this->course_id . '/playlist/' . $token); + $response = $I->sendPost('/courses/' . $this->course_id . '/playlist/' . $token); $I->seeResponseCodeIs(204); // Remove playlist from course diff --git a/tests/PlaylistsCest.php b/tests/PlaylistsCest.php index 7a933ff1d7c79bb2284d1adc9a666e449c875014..9037e5edfe4adf684774c51cb2ff4cdf7b548503 100644 --- a/tests/PlaylistsCest.php +++ b/tests/PlaylistsCest.php @@ -2,9 +2,20 @@ class PlaylistsCest { + private $config_id; + + private $author_name; + private $author_password; + public function _before(ApiTester $I) { - $I->amHttpAuthenticated('apitester', 'apitester'); + $config = $I->getConfig(); + + $this->config_id = $config['config_id']; + $this->author_name = $config['author_name']; + $this->author_password = $config['author_password']; + + $I->amHttpAuthenticated($config['dozent_name'], $config['dozent_password']); } // tests @@ -13,7 +24,8 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -29,7 +41,8 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -51,13 +64,15 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $playlist2 = [ 'title' => 'Meine Videos 2', 'description' => 'Videoliste 2', - 'visibility' => 'free' + 'visibility' => 'free', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -83,7 +98,8 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -171,13 +187,15 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $playlist2 = [ 'title' => 'Meine Videos 2' , 'description' => 'Videoliste 2', - 'visibility' => 'free' + 'visibility' => 'free', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -192,13 +210,13 @@ class PlaylistsCest // give write perms to other user $response = $I->sendPutAsJson('/playlists/' . $token .'/user', [ - 'username' => "apitester_autor1", + 'username' => $this->author_name, 'perm' => 'write' ]); $I->seeResponseCodeIs(200); // then, try to edit it as a different user - $I->amHttpAuthenticated('apitester_autor1', 'apitester_autor1'); + $I->amHttpAuthenticated($this->author_name, $this->author_password); $response = $I->sendPutAsJson('/playlists/' . $token, $playlist2); $I->seeResponseCodeIs(200); @@ -212,13 +230,15 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $playlist2 = [ 'title' => 'Meine Videos 2' , 'description' => 'Videoliste 2', - 'visibility' => 'free' + 'visibility' => 'free', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -233,13 +253,13 @@ class PlaylistsCest // give write perms to other user $response = $I->sendPutAsJson('/playlists/' . $token .'/user', [ - 'username' => "apitester_autor1", + 'username' => $this->author_name, 'perm' => 'read' ]); $I->seeResponseCodeIs(200); // then, try to edit it as a different user - $I->amHttpAuthenticated('apitester_autor1', 'apitester_autor1'); + $I->amHttpAuthenticated($this->author_name, $this->author_password); $response = $I->sendPutAsJson('/playlists/' . $token, $playlist2); $I->seeResponseCodeIs(500); @@ -251,13 +271,15 @@ class PlaylistsCest $playlist = [ 'title' => 'Meine Videos' , 'description' => 'Videoliste', - 'visibility' => 'internal' + 'visibility' => 'internal', + 'config_id' => $this->config_id, ]; $playlist2 = [ 'title' => 'Meine Videos 2' , 'description' => 'Videoliste 2', - 'visibility' => 'free' + 'visibility' => 'free', + 'config_id' => $this->config_id, ]; $response = $I->sendPostAsJson('/playlists', $playlist); @@ -272,17 +294,17 @@ class PlaylistsCest // give write perms to other user $response = $I->sendPutAsJson('/playlists/' . $token .'/user', [ - 'username' => "apitester_autor1", + 'username' => $this->author_name, 'perm' => 'write' ]); $I->seeResponseCodeIs(200); // remove write perms for user - $response = $I->sendDelete('/playlists/' . $token .'/user/apitester_autor1'); + $response = $I->sendDelete('/playlists/' . $token .'/user/' . $this->author_name); $I->seeResponseCodeIs(204); // then, try to edit it as a different user - $I->amHttpAuthenticated('apitester_autor1', 'apitester_autor1'); + $I->amHttpAuthenticated($this->author_name, $this->author_password); $response = $I->sendPutAsJson('/playlists/' . $token, $playlist2); $I->seeResponseCodeIs(500); diff --git a/tests/UsersCest.php b/tests/UsersCest.php index 01c17edad325955b1698a4874583cd4052888eac..a9f95563cf91781c6889abc2aad2115ec14945cd 100644 --- a/tests/UsersCest.php +++ b/tests/UsersCest.php @@ -2,9 +2,15 @@ class UsersCest { + private $dozent_name; + public function _before(ApiTester $I) { - $I->amHttpAuthenticated('apitester', 'apitester'); + $config = $I->getConfig(); + + $this->dozent_name = $config['dozent_name']; + + $I->amHttpAuthenticated($config['dozent_name'], $config['dozent_password']); } // tests @@ -17,7 +23,7 @@ class UsersCest $I->seeResponseContainsJson([ 'type' => 'user', 'data' => [ - 'username' => 'apitester' + 'username' => $this->dozent_name, ] ]); } diff --git a/tests/_support/Helper/Api.php b/tests/_support/Helper/Api.php index 7a4621e8542c3739f981cddb4db6bf7e1f7fbed9..f38fa1daa25b6e096b745690b42a8bce3ee12e49 100644 --- a/tests/_support/Helper/Api.php +++ b/tests/_support/Helper/Api.php @@ -6,5 +6,21 @@ namespace Helper; class Api extends \Codeception\Module { + protected $requiredFields = [ + 'opencast_rest_url', + 'config_id', + 'api_token', + 'opencast_admin_user', + 'opencast_admin_password', + 'dozent_name', + 'dozent_password', + 'course_student', + 'author_name', + 'author_password', + 'course_id', + ]; + public function getConfig(): array { + return $this->config; + } }