diff --git a/About.md b/About.md new file mode 100644 index 0000000..ab7e4d8 --- /dev/null +++ b/About.md @@ -0,0 +1,7 @@ +## About MarkUs + +MarkUs is a web application for the submission and grading of student programming assignments. The primary purpose of MarkUs is to provide TAs with simple tools that will help them to give high quality feedback to students. MarkUs also provides a straight-forward interface for students to submit their work, form groups, and receive feedback. The administrative interface allows instructors to manage groups, organize the grading, and release grades to students. + +Since 2008, more than 120 undergraduate students have participated in the development of MarkUs; some as full-time summer interns, but most working part time on MarkUs as a project course. The fact that we have have uncovered so few major bugs, and that MarkUs has been so well-received by instructors is a testament to the high quality work of these students. MarkUs is used in more than a dozen courses at the University of Toronto, in several courses at the University of Waterloo, and at École Centrale Nantes (in French). + +MarkUs is written using Ruby on Rails, and supports both Git and Subversion to store the student submissions. diff --git a/ApacheMongrel.md b/ApacheMongrel.md deleted file mode 100644 index ff2946d..0000000 --- a/ApacheMongrel.md +++ /dev/null @@ -1,36 +0,0 @@ -Configuration of Apache with Mongrel -==================================== - -Configure the MarkUs application in /config/environments/production.rb (see our MarkUs [configuration documentation](wiki:InstallProd\#Configure) below). **Note:** Please change the "secret" in the cookies related configuration section in config/environment.rb of your MarkUs instance (see <[http://api.rubyonrails.org/classes/ActionController/Session/CookieStore.html](http://api.rubyonrails.org/classes/ActionController/Session/CookieStore.html)\>) - -Configure the mongrel cluster (see config/mongrel\_cluster.yml) and start the mongrel servers: - - mongrel_rails cluster::start # uses config settings defined in config/mongrel_cluster.yml - -The `mongrel_cluster` gem isn't really necessary. It is a nice utility for starting/stopping mongrels for your MarkUs app, though. For more information concerning mongrel clusters see: [http://mongrel.rubyforge.org/wiki/MongrelCluster]([http://mongrel.rubyforge.org/wiki/MongrelCluster)](http://mongrel.rubyforge.org/wiki/MongrelCluster)). - -Configure an httpd VirtualHost similar to the following (Reverse-Proxy-Setup): - - RewriteEngine On - - # define proxy balancer - - BalancerMember http://127.0.0.1:8000 retry=10 - BalancerMember http://127.0.0.1:8001 retry=10 - BalancerMember http://127.0.0.1:8002 retry=10 - - - - DocumentRoot /opt/markus/\/public - - Options FollowSymLinks - AllowOverride None - - /public> - Options Indexes FollowSymLinks MultiViews - AllowOverride None - Order allow,deny - allow from all - - RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f - RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L] \ No newline at end of file diff --git a/ApachePassenger.md b/ApachePassenger.md deleted file mode 100644 index 37e9c5f..0000000 --- a/ApachePassenger.md +++ /dev/null @@ -1,28 +0,0 @@ -Configuration of Apache with Phusion Passenger -============================================== - -Passenger is not interesting when you are in development mode. It is only useful for production.: - - sudo aptitude install libapache2-mod-passenger - sudo passenger-install-apache2-module - -Eventually passenger-install-apache2-module will tell you to copy & paste some settings into the Apache configuration file; something that looks along the lines of: - - LoadModule passenger_module ... - PassengerRoot ... - PassengerRuby ... - -A typical Virtual Host on Apache2 and Passenger : - - - ServerName markus.ec-nantes.fr - DocumentRoot /home/markus/markus/public - - Allow from all - Options -MultiViews - - - -Much more simple than the one with Mongrel :D - -**Be Careful** : There is a bug with Rake and Rails. Passenger use the production environment given with MarkUs. If you have issues loading the schema in the database, just give the same database name to the production and the development environments (in config/databases.yml) \ No newline at end of file diff --git a/AptanaRadRails.md b/AptanaRadRails.md deleted file mode 100644 index 1ed0fc1..0000000 --- a/AptanaRadRails.md +++ /dev/null @@ -1,81 +0,0 @@ -Aptana RadRails -=============== - -You can still install RadRails as an Eclipse plugin, but it is a bit deprecated and using standalone version is easiest - -Install Aptana Radrails ------------------------ - -### Standalone version - -**This tutorial deals with the beta3 version of AptanaRadRails** - -- Go to [http://www.aptana.com/radrails](http://www.aptana.com/radrails) using your preferred Web browser -- Click on "Download Now" -- Select "Standalone" from the drop down selection menu and click on "Download Now" -- Unzip tarball and a execute "Aptana RadRails" - -![Aptana RadRails beta3 with MarkUs](images/Aptana.png "Aptana RadRails beta3 with MarkUs") - -### Install the Radrails Plug-in for Eclipse (optional) - -This tutorial assumes that you have a working installation of Eclipse IDE (preferably Ganymede or later). After having a working Java installation this step should be pretty easy (I usually install the provided Java packages of my distribution). It is suggested to install Eclipse into one's home directory, since Eclipse's built-in plug-in installation system works most seamlessly that way. Downloading the Eclipse tar-ball (for Linux of course) and extracting it in your home directory should suffice. You may want to add the path where your eclipse executable resides to your PATH variable. - -After installing Eclipse, make sure you execute the following command, otherwise you may not be able to install Eclipse plug-ins.: - - #> apt-get install eclipse-pde - -### Install Aptana Radrails - -- Start Eclipse (as normal user, *not* root) -- Go to: “Help” - “Install New Software” -- Click on “Add Site” (*Note:* The next 4 steps for determining the URL to enter next work as of September 15, 2009; Maybe these steps need a little adaption at some point later) -- Go to [http://www.aptana.com/radrails](http://www.aptana.com/radrails) using your preferred Web browser -- Click on "Download Now" -- Select "Eclipse Plugin" from the drop down selection menu and click on "Download Now" -- Record URL mentioned there: e.g. [http://update15.aptana.org/studio/26124](http://update15.aptana.org/studio/26124)/ -- Back in Eclipse: Enter the URL determined by the previous step -- Select (check) “Aptana Studio” from the URL entered as "New Site" previously -- Click "Install..." and click the “Next \>” button -- Read the License Agreement, accept the terms, and click the “Finish \>” button. -- When it is recommended that Eclipse be restarted click “Yes”. -- After the restart, you will be asked to install something from Aptana Studio Site -- Select (check) "Aptana RadRails" and click "Next \>" -- Read the License Agreement, accept the terms, and click the “Next \>” button. -- The downloads should be installed into the .eclipse folder in your home directory by default. If this is acceptable click the “Finish” button. -- Wait for the downloads to complete. -- When it is recommended that Eclipse be restarted click “Yes”. - -Configuration of Aptana RadRails --------------------------------- - -**Check Ruby and Rails Configuration** - -If you are asked if you want to auto-install some gems it is up to you to install them or not (I did). - -- Go to "Window" - "Preferences" -- Select "Ruby" - "Installed Interpreters" -- The selected Ruby interpreter should be in /usr -- Now, go to "Rails" -- Rails should be auto-detected as well as Mongrel - -### Install EGit - -- Again go to: “Help” - “Install New Software...” -- Check if the EGit update site is available (in “Available Software Sites”. Its URL is “http://download.eclipse.org/egit/updates”. -- If not available, click on “Add Site” -- Enter Location: “[http://download.eclipse.org/egit/updates](http://download.eclipse.org/egit/updates)” -- The rest of the installation should be really straight forward. -- Check out the EGit user guide: [http://wiki.eclipse.org/EGit/User\_Guide](http://wiki.eclipse.org/EGit/User_Guide) - -![Aptana RadRails Git](images/Aptana_Git.png "Aptana RadRais beta3 with Git support") -### Checkout MarkUs Source Code - -- Create a Github user account -- Got to [http://github.com/MarkusProject/Markus](http://github.com/MarkusProject/Markus) -- "Fork" the MarkUs repository, by clicking the "Fork" button. -- Figure out the Git clone URL of your fork. Should be something like [git@github.com](mailto:git@github.com):/Markus.git -- Start Eclipse and switch to the RadRails perspective -- Clone the MarkUs repository of your Github fork (use URL as described above) as described here: [http://wiki.eclipse.org/EGit/User\_Guide\#Cloning\_Remote\_Repositories](http://wiki.eclipse.org/EGit/User_Guide#Cloning_Remote_Repositories) - -![Aptana RadRails Project](images/Aptana_Project.png "Aptana RadRails - Project Configuration") \ No newline at end of file diff --git a/Authentication.md b/Authentication.md deleted file mode 100644 index 1a71583..0000000 --- a/Authentication.md +++ /dev/null @@ -1,16 +0,0 @@ -Authentication -============== - -Use Case: - -1. User accesses login page. - - a. If user has an active session, user is redirected to main page. - -2. User inputs user name and password. - - a. User is redirected to login page if user has invalid username or password. - - b. Otherwise, user is directed to main page. - -Authentication is handled by `main_controller.rb`. \ No newline at end of file diff --git a/AutoGenerateSchemaDiagram.md b/AutoGenerateSchemaDiagram.md deleted file mode 100644 index 1bf024b..0000000 --- a/AutoGenerateSchemaDiagram.md +++ /dev/null @@ -1,25 +0,0 @@ -\#Auto generate the database schema - -A plugin exists to autogenerate the database schema, in order to vizualize it in a pdf file: **superdumper** - -You can find the plugin at this address [http://momo.brauchtman.net/2009/02/rails-plugin-superdumper-helps-you-visualize-your-database-schema/]([http://momo.brauchtman.net/2009/02/rails-plugin-superdumper-helps-you-visualize-your-database-schema/](http://momo.brauchtman.net/2009/02/rails-plugin-superdumper-helps-you-visualize-your-database-schema/)) - -This is how you would install the plugin (You will need to have a git client installed in order for this to work): - -> bundle exec script/plugin install git://github.com/moritzh/superdumper.git - -[You will also need a copy of GraphViz to generate the schema PDF]([http://www.graphviz.org/Download..php](http://www.graphviz.org/Download..php)) - -You can launch the autogeneration by typing - -> bundle exec rake db:superdumper - -If you want to generate the dot file *and* the pdf file, edit - -> /vendor/plugins/superdumper/tasks/superdumper\_tasks.rake - -and comment out the following line - -> File.delete("\#{RAILS\_ROOT}/database.dot") - -It will then generate both, the dot file and the pdf file. \ No newline at end of file diff --git a/CommonInterface.md b/CommonInterface.md deleted file mode 100644 index 116fa0d..0000000 --- a/CommonInterface.md +++ /dev/null @@ -1,66 +0,0 @@ -**Criteria: Common Interface** -=================== -After an assignment is created, an admin can create one or more criteria for it. These criteria can be of any type (rubric, flexible, checkbox). - -> **Note:** We use the term "criteria", to refer to either rubric criteria, flexible criteria or checkbox criteria, which have different settings, but serve the same purpose of guiding the marking for an assignment. - -We have created a common interface for our criteria, so that we can use the same method name to access methods that have the same functionality, but different implementation depending on the type of criterion. These methods are defined under their appropriate criterion class. - ----------- - - Common Attributes -=================== -The following attributes are shared among all types of criteria: - --- **name** -: The name of the criterion. - --- **position** -: The position of the criterion. - --- **assignment_id** -: The id of the assignment to which this criterion belongs. - --- **max_mark** -: The maximum mark a student can achieve for this particular criterion. - --- **ta_visible** -: Determines if the criterion is visible to teaching assistants or not. - --- **peer_visible** -: Determines if the criterion is visible to peer reviewers or not. - ----------- - - Methods -=================== -All the methods below are defined in all of the criteria models. - -#### **Class methods** -: --- **load_from_yml(criterion_yml)** -:    Instantiates the appropriate criterion from a YML entry. -: --- **to_yml(criterion)** -:    Returns a hash containing the information of an appropriate single criterion. -: --- **symbol** -:    Returns a Ruby symbol for the appropriate criterion class. Eg: -:      FlexibleCriterion.symbol #=> :flexible -: -> **Note:** These methods are called on a criterion class, and therefore, they are called as follows: -> RubricCriterion.shared_class_method(arguments) -> FlexibleCriterion.shared_class_method(arguments) -> CheckboxCriterion.shared_class_method(arguments) - -#### **Instance methods** -: --- **set_mark_by_criterion(mark_to_change, mark_value)** -:    Sets the mark for a criterion appropriately, depending on its type. -: --- **weight** -:    Returns the weight of the criterion. -: -> **Note:** These methods are called on an instance of a class, and therefore, they are called as follows: -> my_instance = RubricCriterion.create(name: 'My Rubric criterion') -> my_instance.instance_method(parameters) diff --git a/Configuration.md b/Configuration.md new file mode 100644 index 0000000..9ed8154 --- /dev/null +++ b/Configuration.md @@ -0,0 +1,165 @@ +# Configuration Settings + +_new in version 1.12.0_ + +Custom configuration settings for MarkUs can be set by adding a `config/settings.local.yml` file. +Values in this file are described below and will override any default values. + + +#### Default Values + +To show the default values for your environment, run the following command in the root directory of the installed MarkUs instance: + +```sh +echo 'puts JSON.parse(Settings.to_json).to_yaml' | bundle exec rails console +``` + +#### Settings + +All values under the `rails:` key are used to set the `Rails.configuration` object when the app starts. + +For example, the `queue_adapter` option sets `Rails.configuration.queue_adapter`, and `asset_host` sets `Rails.configuration.action_mailer.asset_host`. For full details see [Rails Guides](https://guides.rubyonrails.org/configuring.html) + +```yaml +rails: + time_zone: # time zone string (supported by ActiveSupport::TimeZone) + active_job: + queue_adapter: # queue adapter name (supported by ActiveJob::QueueAdapters) + assets: + prefix: # relative path from the rails root to write compiled assets to + active_record: + verbose_query_logs: # boolean indicating whether to write verbose query logs + session_store: + type: # session store name (supported by ActionDispatch::Session) + args: # hash of arguments used to initialize the session store class (this may vary by type) + action_mailer: + delivery_method: # action mailer delivery method (supported by ActionMailer::Base) + default_url_options: + host: # mail server host + asset_host: # mail asset host + perform_deliveries: # boolean indicating whether to send mail or not + deliver_later_queue_name: # name of queue used to send mail as a background job + sendmail_settings: (required if delivery_method == sendmail) hash containing sendmail settings + smtp_settings: (required if delivery_method == smtp) hash containing smtp settings + file_settings: (required if delivery_method == file) hash containing file settings + cache_classes: # boolean indicating whether classes should be reloaded if they change + eager_load: # boolean indicating whether to eager load namespaces + consider_all_requests_local: # boolean indicating whether to display detailed debugging information on an error + hosts: # (optional) list of hosts to allow when checking for Host header attacks (if empty, no checks are made) + log_level: # log level + force_ssl: # boolean indicating that all traffic must be sent over ssl + active_support: + deprecation: # string indicating where to write deprecation warnings + cache_store: # cache store name + action_controller: + perform_caching: boolean indicating whether to enable fragment caching (enable this for production only) +queues: + default: # name of the queue to use as default for background jobs (see "Additional Queue Names" below) +redis: + url: # url of a running redis service +course_name: # the name of the course using this MarkUs instance +validate_file: # absolute path to the validation script used to validate users on login +validate_ip: # boolean indicating whether to pass the user's ip address to the validation script +validate_custom_exit_status: # custom exit status returned by the validation script +validate_custom_status_message: # message to display to the user if the validation script returns the custom exit status +remote_user_auth: # see "Remote User Authorization" below +logout_redirect: # url to redirect to when a user logs out of MarkUs, 'DEFAULT' will redirect to the login page, 'NONE' will redirect to a 404 page. +repository: + type: # repository type used to store student submissions. Choose from 'git', 'svn', 'mem' + url: # (required if type == git or svn) base url used to remotely access a repository over http/https + ssh_url: # (required if type == git and enable_key_storage == true) base url used to remotely access a repository over ssh + is_repository_admin: # boolean indicating whether MarkUs manages repositories + storage: # absolute path to the directory where repositories are stored +max_file_size: # maximum file size (in bytes) allowed to be uploaded through the web interface +student_session_timeout: # duration of a student user's session (in seconds) +ta_session_timeout: # duration of a grader user's session (in seconds) +admin_session_timeout: # duration of an admin user's session (in seconds) +enable_key_storage: # boolean indicating whether to allow ssh public key uploads +key_storage: # absolute path to a directory to store ssh public key uploads +student_csv_upload_order: # column order of student csv upload file (choices are: user_name, last_name, first_name, section_name, id_number, email) +ta_csv_upload_order: # column order of grader csv upload file (choices are: user_name, last_name, first_name, email) +logging: + enabled: # boolean indicating whether to enable logging + rotate_by_interval: # boolean whether to rotate logs + rotate_interval: # (required if rotate_by_interval == true) interval used to rotate logs (choose from: daily, weekly, monthly) + size_threshold: # (required if rotate_by_interval == false) maximum file size (in bytes) of a single log file + old_files: # maximum number of log files to keep (older files will be deleted) + log_file: # relative path (from the MarkUs root) to the log file + error_file: # relative path (from the MarkUs root) to the error log file +autotest: + enable: # boolean to indicate whether to enable autotests + student_test_buffer_minutes: # maximum number of minutes between student tests (see "Student Tests" below) + url: # url of the autotester API + client_dir: # absolute path to a directory to store local autotesting files + max_batch_size: # maximum number of tests to send to the markus-autotesting server in a single batch +scanned_exams: + enable: # boolean indicating whether to enable scanned exams + path: # absolute path to a directory to store scanned exam files +i18n: + available_locales: # list of locale strings (choose from: en, es, pt, fr) (Note that en is the only option that is fully supported) + default_locale: # locale string to use as default (must be one of the options in available_locales) +validate_user_not_allowed_message: # custom message to display to users who fail validation on login +incorrect_login_message: # custom message to display to users who enter an incorrect username +starter_file: + storage: # absolute path to a directory to store starter files +python: + bin: # location of the bin subdirectory of the python3 virtual environment where python dependencies are installed +pandoc: # path to the pandoc executable +``` + +#### Additional queue names + +By default, background jobs will be run using the queue specified by the + +```yaml +queue: + default: +``` + +setting. If you would like to use different queue names for different background jobs, you can specify additional keys (the background job name written in snake case) under the `queue:` key. + +For example, the following conifguration: + +```yaml +queue: + default: default + autotest_specs_job: specs_queue + split_pdf_job: some_other_one +``` + +Will run all background jobs using a queue named "default" except for `AutotestSpectsJob` which will use a queue named "specs_queue" and `SplitPdfJob` which will use a queue named "some_other_one". + +#### Remote User Authorization + +If the `remote_user_auth:` setting is false, MarkUs will attempt to validate a user by passing their user_name and password (and optionally their IP address) to the provided validation script and parsing the exit status of that script. + +If the `remote_user_auth:` setting is true, MarkUs assumes that users have already been authenticated and will use the user name supplied by `request.env['HTTP_X_FORWARDED_USER']` to log in. + +Note that in development mode, if the `remote_user_auth:` setting is false and `request.env['HTTP_X_FORWARDED_USER']` is not set then [`authenticate_or_request_with_http_basic`](https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Basic/ControllerMethods.html#method-i-authenticate_or_request_with_http_basic) will be used to perform authentication (any password is valid in this case). + +#### Student Tests + +Students are only allowed to run one test at a time. This means that a student must wait until the results from a previous test have returned before they can run another one. If a test result never returns (because of an unexpected error) a student will have to wait `student_test_buffer_minutes` before they can run a new test. + +#### Environment variables + +All of the settings described above can also be set using environment variables. Environment variables start with `MARKUS__` followed by each nested yaml key separated by `__`. For example, + +```sh +MARKUS__REDIS__URL=redis://localhost:6379/1 +``` + +is equivalent to setting this in a yml file: + +```yaml +redis: + url: 'redis://localhost:6379/1' +``` + +Any setting set by an environment variable will override a setting set in a yml file. + +One setting option can only be changed by an environment variable. To set the relative url root for your MarkUs instance, you must set the `RAILS_RELATIVE_URL_ROOT` environment variable. For example, if your relative url root is `/csc108` then you can start the rails server as: + +```sh +RAILS_RELATIVE_URL_ROOT=/csc108 bundle exec rails server +``` diff --git a/ConfigureMarkUs.md b/ConfigureMarkUs.md deleted file mode 100644 index 2f5d5f8..0000000 --- a/ConfigureMarkUs.md +++ /dev/null @@ -1,60 +0,0 @@ -Configure MarkUs ----------------- - -Precondition: You have the MarkUs source-code checked out and do not plan to use RadRails (see the following sections if you *plan* to use RadRails for development). - -- Look at `config/environments/development.rb` - -- Change the REPOSITORY\_STORAGE path to an appropriate path for your setup. NOTE: it is unlikely that you need to change these values for development - -Configure the database ------------------------ -For postgres, setup the database.yml file, in the MarkUs' root directory: - -- \`cp config/database.yml.postgresql config/database.yml\` - -- in config/database.yml, make sure that "development:" and the 5 lines under it (adapter, encoding, database, username, password) are not commented out - -- do the same for "test:" and the 5 lines under it to be able to run unit tests and functional tests - -- change the usernames and password (in development and test) to the ones you used in the section above ('markus' if you copy/pasted the instructions) - -For [mysql](SettingUpMySQL), see the MarkUs configuration instructions in the relevant wiki pages. - - -Test plain MarkUs installation ------------------------------- - -If you followed the above installation instructions in order, you should have a working MarkUs installation (in terms of required software and required configuration). But first you would need to create the development database, load relations into it and populate the db with some data. You can do so by the following series of commands (as non-root user, assuming you are in the application-root of the MarkUs source code;)(please adapt the following command): - - # gets gems that you do not have yet, like thoughtbot-shoulda - $> bundle install --without (postgresql) (mysql) - $> bundle exec rake db:setup # creates, initializes, and populates all the databases uncommented in config/database.yml - $> bundle exec rake test - -Note: if you are using RVM, follow [these instuctions](RVM)\_ to install subversion into the correct path - -Now, you are ready to test your plain MarkUs installation. The most straight forward way to do this is to start the mongrel server on the command-line. You can do so by: - - $> bundle exec rails server #boots up the apprpropriate web server - -The default admin user is 'a' with any non-empty password. Look at `db/seeds.rb` for other users. - -If this doesn't work try - - $> rails s - -**Common Problems** - -If some of the previous commands fail with error message similar to `LoadError: no such file to load -- `, try to install the missing Ruby gem by issuing `gem install ` and retry the step which failed. - -If everything above went fine: Congratulations! You have a working MarkUs installation. Go to [http://0.0.0.0:3000](http://0.0.0.0:3000)/ and enjoy MarkUs! - -However, since you are a MarkUs developer, this is only *half* of the game. You also **need** (yes, this is not optional!) *some* sort of IDE for MarkUs development. For instance, the next section describes how to install RadRails IDE, an Eclipse based Rails development environment. If you plan to use something *else* for MarkUs development, such as JEdit (with some tweaks) or VIM, you should now start configuring them. - -But if you *do* plan to use RadRails for development, you should get rid of some left-overs from previous steps, so that the following instructions run as smoothly as possible for you. This is what you'd need to do (If you know what you are doing, you might find this silly. But this guide tries to give detailed instructions for Rails newcomers): - - $> bundle exec rake db:drop # get rid of the database, created previously (it'll be recreated again later) - $> rm -rf markus_trunk # get rid of the MarkUs source code possibly checked out previously (you might do a "cd .." prior to that) - -**Happy Coding!** diff --git a/Deprecated-wiki-pages.md b/Deprecated-wiki-pages.md new file mode 100644 index 0000000..08673b3 --- /dev/null +++ b/Deprecated-wiki-pages.md @@ -0,0 +1,112 @@ + + + +## Installation: setting up MarkUs as a git ssh server + +_before version 1.12.0_ + +To enable key pair uploads and to allow the server running MarkUs to handle git requests over ssh: + +In `config/environments/production.rb` set: + +``` +config.enable_key_storage = true +``` + +For the rest of the setup we will assume that: + +``` +config.key_storage = "/MarkUs/data/prod/keys" +config.x.repository.storage = "/MarkUs/data/prod/repos" +``` + +and that you're running MarkUs on a server named `markus.example.edu`. + +1. Make sure the required packages are installed: + +```sh +$ apt-get install openssh-server git +``` + +2. Create a user to serve the repositories (typically this user is named `git`) + +```sh +$ useradd -m -s /bin/bash git +``` + +3. Put the `markus-git-shell.sh` script on the the new user's PATH and make it executable: + +```sh +$ cp .dockerfiles/markus-git-shell.sh /usr/local/bin/markus-git-shell.sh +$ chown git:git /usr/local/bin/markus-git-shell.sh +$ chmod 700 /usr/local/bin/markus-git-shell.sh +``` + +4. The `markus-git-shell.sh` calls `git-shell` which the git user needs to run with super-user permissions: + +```sh +$ echo "git ALL=(root) NOPASSWD:/usr/bin/git-shell" | sudo EDITOR="tee -a" visudo +``` + +5. Make symlinks of all relevant files: + +If your MarkUs instance does not use a relative url root: + +```sh +$ ln -s /MarkUs/data/prod/keys/ /home/git/.ssh/ +$ ln -s /Markus/data/prod/repos/bare/ /home/git/ +``` + +OR if your instance uses a relative url root (ex: csc108/) + +```sh +$ ln -s /MarkUs/data/prod/keys/ /home/git/.ssh/csc108/ +$ ln -s /Markus/data/prod/repos/bare/ /home/git/csc108/ +$ sed -i "s@#*AuthorizedKeysFile.*@AuthorizedKeysFile /home/git/.ssh/csc108/authorized_keys@g" /etc/ssh/sshd_config +``` + +6. Start the sshd service: + +```sh +$ /usr/sbin/sshd +``` + +7. Set remaining config option: + +``` +config.x.repository.ssh_url = git@markus.example.edu +``` + +OR if you're using a relative url root make sure to include it: + +``` +config.x.repository.ssh_url = git@markus.example.edu/csc108 +``` + +## Installation: Set up nbconvert virtual environment + +_before version 1.13.0_ + +MarkUs uses python's nbconvert package to convert jupyter notebooks to html so it can be displayed in the browser. Install a python virtual environment with nbconvert installed. This virtual environment can be installed anywhere, in this example it is created at `/some/dir/venv` + +```bash +python3 -m venv /some/dir/venv +/some/dir/venv/bin/pip install wheel nbconvert +``` + +Let MarkUs know where the virtual environment is installed. Set `config.nbconvert` in `production.rb`: + +```ruby +config.nbconvert = '/some/dir/venv/bin/jupyter-nbconvert' +``` + +## Installation: create python virtual environment and download required packages + +_before version 1.13.0_ + +```bash +python3.7 -m venv lib/scanner/venv +source lib/scanner/venv/bin/activate +pip install -r lib/scanner/requirements.txt +deactivate +``` diff --git a/Developer-Guide--Action-Policy-Style-Guide.md b/Developer-Guide--Action-Policy-Style-Guide.md new file mode 100644 index 0000000..39d7d91 --- /dev/null +++ b/Developer-Guide--Action-Policy-Style-Guide.md @@ -0,0 +1,203 @@ +# Action Policy Style Guide + +## About + +MarkUs manages authorization and permissions using the [ActionPolicy gem](https://actionpolicy.evilmartians.io). + +Before any controller route is accessed, the corresponding policy determines whether the current user has permission to access that route. + +Policies can also be used to check whether a user has permission to access a specific resource or perform a specific action. These policies can be called in controller methods at any time. + +Before reading the rest of this style guide, make sure you are familiar with ActionPolicy by reading the [documentation](https://actionpolicy.evilmartians.io). It is especially important to understand the section on [Rails integration](https://actionpolicy.evilmartians.io/#/rails?id=using-with-rails) + +### File locations + +Policy files can be found under: `app/policies` + +Policy translation files can be found under: `config/locales/policies` + +Rspec tests can be found under: `spec/policies` + +## Guidelines for writing policies + +#### All controller routes must have a corresponding policy + +If you create or rename a route, you *must* create or rename the corresponding policy. In the `ApplicationController` class there is the [`verify_authorized`](https://actionpolicy.evilmartians.io/#/rails?id=verify_authorized-hooks) hook which will raise an error if a user tries to access a route and a policy is not checked. + + +#### Controllers must all have an implicit_authorization_target + +MarkUs tries to use [resourceless authorization](https://actionpolicy.evilmartians.io/#/rails?id=resource-less-authorize) whenever possible. This means that every time ActionPolicy authorizes a route, we do not need to specify which policy it needs to use. + +Instead, we can define an [`implicit_authorization_target`](https://actionpolicy.evilmartians.io/#/behaviour?id=implicit-authorization-target) method for each controller. + +By default, controllers will inherit this method from `ApplicationController` but in some cases it may be necessary to override this method for a subclass. For example, if a controller does not have a corresponding model, the default `implicit_authorization_target` will need to be overwritten since it assumes the existance of that model. + + +#### Policy names should reflect their purpose + +Policy classes should be named the same as the corresponding controller, except the policy class name should be singular (not plural) and "Controller" should be replaced with "Policy". + +If a policy function is used to determine whether a user has access to a route, the policy function should be named the same as the route followed by a question mark. For example: + +```ruby +class ExamplesController < ApplicationController + def index + end +end + +class ExamplePolicy < ApplicationPolicy + def index? + end +end +``` + +If a policy function is used for some other purpose (determine whether a user has access to a resource or can perform a specific action), the policy name should describe the purpose. + +For example, a policy that checks whether a student is allowed to work alone in a group is named `work_alone?`. A good rule when trying to think of policy names is: does the name make sense if inserted into the sentence: + +"Is this user allowed to \_\_\_\_\_\_?" + + +#### All policy functions must have a failure reason + +If you create or rename a policy function, make sure to create or update the internationalization strings for that policy function (in `config/locales/policies`) + + +#### Use `allowed_to?` and `check?` in different contexts + +The `allowed_to?` function has an alias `check?`. However, to improve clarity of code, `check?` should be only used in policy classes and `allowed_to?` should only be used outside of policy classes (in controllers or views). + + +#### Be purposeful about calling `check?` + +When a policy fails (returns false), the [failure reasons](https://actionpolicy.evilmartians.io/#/reasons?id=failure-reasons) are collected in the policy's `result` attribute. + +Failure reasons are added every time a `check?` or `allowed_to?` function returns false. Because of this, it is important to think about how policy functions call each other and what failure reasons you want to include. For example: + +```yml +en: + action_policy: + policy: + example: + index?: "You don't have access to the index route." + be_an_admin?: "You are not an admin user" + other: + be_an_admin?: "You are still not an admin user" + be_a_ta_or_student?: "You are not a TA or a student" +``` + +Scenario 1: + +```ruby +class ExamplePolicy < ApplicationPolicy + def index? + check?(:be_an_admin?) + end + + def be_an_admin? + user.admin? + end +end +``` + +If the `index?` policy fails the error messages will be: `["You don't have access to the index route.", "You are not an admin user"]` becuase both the `index?` and `admin?` policies are called. + +Scenario 2: + +```ruby +class ExamplePolicy < ApplicationPolicy + def index? + user.admin? + end + + def be_an_admin? + user.admin? + end +end +``` + +If the `index?` policy fails the error messages will be: `["You don't have access to the index route."]` because only the `index?` policy is called. + + +Note that failure reasons will only be added if `check?` or `allowed_to?` is called in the original policy class. For example: + +```ruby +class ExamplePolicy < ApplicationPolicy + def index? + check?(:admin?, with: OtherPolicy) + end +end + +class OtherPolicy < ApplicationPolicy + def be_an_admin? + !check?(:be_a_ta_or_student?) + end + + def be_a_ta_or_student? + user.ta? || user.student? + end +end +``` +If the `index?` policy fails, the error messages will be: `["You don't have access to the index route.", "You are still not an admin user"]`. It will not include `"You are not a TA or a student"` because the `check?` function was called in a policy class different to the one that `index?` is in. + + +#### Include additional context for policies if needed + +If a policy requires additional context that can be provided using [explicit additional context](https://actionpolicy.evilmartians.io/#/authorization_context?id=explicit-context). + +For example: + +```ruby +class ExamplePolicy < ApplicationPolicy + authorize :submission + + def index? + submission.revision_identifier.present? + end +end +``` + +In the example above, the `index?` policy needs to check a submission object. Then when calling this policy the submission object can be passed as part of the `context` keyword: + +```ruby +allowed_to?(:index?, context: { submission: Submission.find(10) }) +``` + +#### Writing Tests + +Rspec tests written for policies should use [Action Policies' Rspec DSL](https://actionpolicy.evilmartians.io/#/testing?id=rspec-dsl). + +For clarity, we prefer to not nest `succeed` or `failed` blocks within each other. For example, the following two test classes are functionally equivalent but the second one is preferred: + + +```ruby +describe NotePolicy do + let(:context) { { user: user } } + let(:record) { create :note } + describe_rule :manage? do + failed 'user is a ta' do + let(:user) { create :ta } + succeed 'when the note is created by the ta' do # <- succeed is nested in a failed block (DO NOT DO THIS) + let(:record) { create :note, user: user } + end + end + end +end +``` + +```ruby +describe NotePolicy do + let(:context) { { user: user } } + let(:record) { create :note } + describe_rule :manage? do + context 'user is a ta' do + let(:user) { create :ta } + failed 'when the note is not created by the ta' # <- failed and succeed are nested in a context block (DO THIS) + succeed 'when the note is created by the ta' do + let(:record) { create :note, user: user } + end + end + end +end +``` diff --git a/Developer-Guide--Guidelines.md b/Developer-Guide--Guidelines.md new file mode 100644 index 0000000..2dba28b --- /dev/null +++ b/Developer-Guide--Guidelines.md @@ -0,0 +1,53 @@ +# Guidelines for MarkUs Development + +## Motivation + +MarkUs is mainly developed by Computer Science students who volunteer to work on MarkUs, work on it for course credit or work full-time on MarkUs during their summer break. Whether developers work on Markus full-time or part-time, they usually work on it for one semester (4 months). So since development teams change pretty frequently and the MarkUs code-base grows constantly, it is inevitable for developers to stick to some basic rules in order to maintain or increase code quality. + +## Development Process + +When developing MarkUs, make sure to follow the following steps (where appropriate): + +1. Write a plan for your changes and discuss them with the other MarkUs developers and maintainers. +2. Create a branch based off of the master branch and make a draft pull request for this branch. +3. Make your proposed changes and push the changes to your branch. If your changes differ from your original plan, update your plan and discuss the updates with your colleagues. +4. Write tests for your changes. +5. Write documentation for your changes. +6. Mark your pull request as "Ready for review" and request a review from one of the maintainers. +7. Make changes as necessary. + +### DOs + +- **Do** use Rails tools (such as [generators](http://wiki.rubyonrails.org/rails/pages/AvailableGenerators)) when appropriate in order to have code-stubs generated and for [migrations](http://guides.rubyonrails.org/migrations.html) +- **Do** use the [debugger](http://guides.rubyonrails.org/debugging_rails_applications.html). +- **Do** document your code appropriately. Add or update method-level and class-level docstrings as required. You can assume that the reader is familiar with Ruby and Rails. If your code requires more extensive documentation, you may wish to add or update a Wiki page. Remember, once you are done with your work and you leave the project, new developers should be able to use what you have contributed without a lot of effort. +- **Do** ask for help. Ask questions commenting on your pull requests, emailing or in talking to the maintainers in person (if you can). But don't wait until the project is almost finished; problems can often be resolved quickly by sharing your code and asking questions. +- **Do** write tests! (see the [guides](#guides) for testing guides) + +### DON'Ts + +1. **Don't** mark your pull request as "Ready for review" until you're entirely happy with it. Go, have a break and come back to your code after a while. Questions you should ask yourself are: Is my controller code really controller code, or should it be moved to a model? Is there a simpler solution? Can Rails help with what I am trying to achieve? +2. **Don't** mull over problems alone for hours/days. Sometimes it's better to consult somebody else: two pairs of eyes see more than one. Maybe somebody else has had a similar problem, etc. Go ask questions! +3. **Don't** fight Rails (it'll beat you). Sometimes Rails' "magic" is irritating. However, you are better off *using* rather then fighting it! +4. **Don't** use absolute paths/url's in any code (use `url_for` instead). *Always* let Rails generate URLs. You have to assume that there are more than one MarkUs applications running on a server, once deployed. Rails does a really good job on this, so use it. + +## Code Styleguide + +- Use 2 (two) **spaces** (instead of tabs) for indenting +- Make sure you provide a brief and understandable high-level-description of your Rails model/controller code (use RDoc syntax, where appropriate)). + +## Guides + +Rails: + +- http://guides.rubyonrails.org/ +- http://www.caliban.org/ruby/rubyguide.shtml + +Rspec: + +- https://www.rubyguides.com/2018/07/rspec-tutorial/ +- https://www.betterspecs.org/ + +Action Policy: + +- https://actionpolicy.evilmartians.io diff --git a/Developer-Guide--Release-Instructions.md b/Developer-Guide--Release-Instructions.md new file mode 100644 index 0000000..d7c854e --- /dev/null +++ b/Developer-Guide--Release-Instructions.md @@ -0,0 +1,27 @@ +# Release Instructions + +#### When reviewing a pull request (PR): +* decide whether the changes made PR should be released with the next *minor* or *major* version (see below) +* if a PR should be released with the next major version no additional steps need to be taken. However, please ensure that the PR targets the `master` branch. +* if a PR should be released with the next minor version: + * make sure the current PR targets the `master` branch. + * make an identical (or as close as possible) PR that also target the release candidate branch for the upcoming release. + * add the PR that targets the release candidate branch to the milestone for the upcoming release. + +#### When making a major release: +1. merge the `master` branch into the `release` branch and resolve all conflicts. +2. update the Changelog.md files in both the `release` and `master` branches by replacing the `[unreleased]` section with the new release's version number. +3. fully test the `release` branch in development. +4. update the app/MARKUS_VERSION file in the `release` and `master` branches. +5. make a new release targeting the `release` branch (mark it as a pre-release). +6. deploy the new release to a test instance on a production server and test the instance there as well. +7. un-mark the new release as a pre-release + +#### When making a minor release: +1. merge the current release candidate branch into the `release` branch (there should be no conflicts). +2. update the Changelog.md file in the both the `release` and `master` branches by creating a new section for the new release and moving all relevant lines from the `[unreleased]` section to this new section. +3. do steps 3-7 from the [major release](#when-making-a-major-release) section above + +#### After making any release: +1. close any existing milestones and create a new milestone for the next minor release. For example, if you just released version 1.9.5, make a new milestone named `v1.9.6`. +2. delete the old release candidate branch (if it exists) and create a new release candidate branch based off of the current `release` branch. For example, if you just released version 1.9.5, delete the branch named `v1.9.5.rc` and create a branch named `v1.9.6.rc` diff --git a/Developer-Guide--Set-Up-With-Docker.md b/Developer-Guide--Set-Up-With-Docker.md new file mode 100644 index 0000000..5fc50e7 --- /dev/null +++ b/Developer-Guide--Set-Up-With-Docker.md @@ -0,0 +1,171 @@ +## Downloading and Installing + +If you want to get started on working on MarkUs quickly and painlessly, this is the way to do it. + +1. If you are using **Windows**, you will need to install Windows Subsystem for Linux (WSL) by following the instructions on [this page](https://docs.microsoft.com/en-us/windows/wsl/install-win10). (The "Simplified Installation" section is probably easiest, but you need to join the Windows Insiders Program with a Microsoft account.) + + - If you are given a choice of what operating system to use, select *Ubuntu 20.04*. + +2. Install [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/). + + - On Windows, make sure you've selected the "WSL 2 backend" tab under "System Requirements" and follow those instructions. + - On Linux, also follow the instructions on "Manage Docker as a non-root user" [here](https://docs.docker.com/install/linux/linux-postinstall/). + +3. If you are using **Windows**, you'll need to open a terminal into the WSL system you installed. *This is the terminal where you'll type in the rest of the commands in this section.* + + 1. To start the WSL terminal, open the start menu and type in "ubuntu". Click on the "Ubuntu 20.04" application. (We recommend pinning this to your taskbar to make it easier to find in the future.) + 2. Type in the command `pwd`, which shows what folder you're currently in. You should see `/home/` printed. If it isn't, switch to your home directory using the command `cd ~`. + +4. Clone the Markus repository from GitHub by following the instructions in [Setting up Git and MarkUs](Developer-Guide--Setting-up-Git.md). (This is a document you will want to read very carefully and may come back to.) + +5. Change into the repository that you just cloned: `cd Markus`. + +6. Run `docker-compose build app`. + +7. Run `docker-compose up rails`. The first time you run this it will take a long time because it'll install all of MarkUs' dependencies, and then seed the MarkUs application with sample data before actually running the server. When the server actually starts, you'll see some terminal output that looks like: + + ``` + Puma starting in cluster mode... + * Version 4.3.1 (ruby 2.5.3-p105), codename: Mysterious Traveller + * Min threads: 0, max threads: 16 + * Environment: development + * Process workers: 3 + * Preloading application + * Listening on tcp://0.0.0.0:3000 + Use Ctrl-C to stop + - Worker 0 (pid: 185) booted, phase: 0 + - Worker 1 (pid: 193) booted, phase: 0 + - Worker 2 (pid: 201) booted, phase: 0 + ``` + + 1. On **Windows**, open a separate WSL terminal (but leave the current one running), and type in the command `docker-compose run --rm rails bash`. This will take you into the Docker container; you'll see the prompt change to `app/$`. + + 2. Run the command `rails js:routes`, which will take a moment to generate a required file. + +8. Open your web browser and type in the URL `localhost:3000/csc108`. The initial page load might be slow, but eventually you should see a login page. Use the username `a` and any non-empty password to login. + + *Tip*: to terminate the Rails server, go to the terminal window where the server is running and press `Ctrl + C`/`⌘ + C`. + + - On Windows Home Edition, you'll need to use the Docker container's IP address instead: `192.168.99.100:3000/csc108`. + +9. In a new terminal window, go into the Markus directory again and run `docker-compose run rails rspec` to run the MarkUs test suite. This will take several minutes to run, but all tests should pass. + + **Troubleshooting** + + - If you see a test failing with the following message near the top: + + ``` + 1) SubmissionsController#get_file When the file is a jupyter notebook file should download the file as is + Failure/Error: _stdout, stderr, status = Open3.capture3(*args, stdin_data: file_contents) + + Errno::ENOENT: + No such file or directory - /app/nbconvertvenv/bin/jupyter-nbconvert + ``` + + Run the following commands: + + ```console + $ docker-compose run --rm rails bash # This takes you into the Docker container + $ python3.8 -m venv /app/nbconvertvenv + $ /app/nbconvertvenv/bin/pip install wheel nbconvert + ``` + + Then try re-running the tests. You can do this from your current terminal (inside the Docker container) simply by running `rspec`. + +Hooray! You have MarkUs up and running. Please keep reading for our recommended developer setup. + +## Installing and Configuring RubyMine + +We strongly recommend RubyMine (a JetBrains IDE) for all MarkUs development. + +1. First, install RubyMine from [here](https://www.jetbrains.com/ruby/download). Note that if you are a current university student, you can obtain a [free license](https://www.jetbrains.com/student/) for all JetBrains software. + +2. Open the MarkUs repository in RubyMine. + + - On Windows, your repository will be located at `\\wsl$\Ubuntu-20.04\home\\Markus`. + +3. Complete the setup steps under [Docker: Enable Docker Support JetBrains guide](https://www.jetbrains.com/help/ruby/docker.html#enable_docker). + +4. To configure RubyMine to use a remote Ruby interpreter from the Docker image: [JetBrains guide](https://www.jetbrains.com/help/ruby/using-docker-compose-as-a-remote-interpreter.html#set_compose_remote_interpreter). Use `rails` as the service. After you've selected this interpreter, RubyMine will take some time to index all of the Ruby gems (libraries); you'll see "Indexing"... at the bottom of the RubyMine window. + + If this doesn't work, please make sure you're using the latest version of RubyMine (Help -> Check for Updates...). + +5. To configure RubyMine to connect to the PostgreSQL database that MarkUs uses for development, first make sure the MarkUs server is running (by doing a `docker-compose run...` as above). Then follow [these instructions](https://www.jetbrains.com/help/idea/running-a-dbms-image.html#6aa07130) in RubyMine to connect to the PostgreSQL server running as the 'postgres' docker-compose service. Note that you do not need to create a new container so you should only need to follow the instructions under "Connect to the PostgreSQL server". + + - hostname: `localhost` + - port: `35432` + - user: `postgres` + - password: `docker` + - database: `markus_development` + +## Installing Pre-Commit Hooks + +We use [pre-commit](https://pre-commit.com/) to run automated checks on code before each commit. To set this up on your local computer (not in Docker): + +1. First, install Python 3. +2. Then, install the pre-commit library: `$ python3 -m pip install pre-commit` (or just `python` instead of `python3`, depending on your Python executable. +3. Finally, in the `Markus` folder run `$ pre-commit install`. This will install all of the Markus pre-commit hooks. + +After this, these checks will run every time you make a commit. If all checks pass, the commit will proceed as normal. If a check fails, the commit *does not* occur, and there are two possibilities: + +- Some checks will automatically fix issues (e.g., most style checks). *These changes still need to be manually git added and committed!* +- Some checks will just report problems that you'll need to fix manually. After fixing them, you'll need to add and commit those changes. + +## Running Commands in Docker + +Here's a summary of the few most common tasks you'll use in your development. + +- Start the MarkUs server: `docker-compose up --no-recreate rails` +- Run the MarkUs test suite: `docker-compose run rails rspec` +- Run a specific test file: `docker-compose run rails rspec FILE` +- Start a shell within the Docker Rails environment: `docker-compose run --rm rails bash`. + Within this shell, you can: + - Install new dependencies: `bundle install`, `yarn install` + - Reset the MarkUs database: `rails db:reset` + - Run a database migration: `rails db:migrate` + - Start the interactive Rails console: `rails c` + +Here's a summary of a few commands that are helpful for managing containers. + +- Stop the MarkUs server: `docker-compose stop rails` +- Start the MarkUs server up again (after stopping it): `docker-compose start rails` +- Remove all containers started by MarkUs: `docker-compose down` +- Remove all containers and all volumes started by MarkUs: `docker-compose down -v` + - Note that removing volumes will mean that you will lose all changes made in the database + +If you need to rebuild the MarkUs docker image: + +- Stop and remove the existing containers and remove all volumes: `docker-compose down -v` +- Do steps 5 and 6 from the Downloading and Installing section [above](#downloading-and-installing) + +## Setting up the autotester (DRAFT) + +**Note**: you only need to consult this section if you'll be working with the MarkUs autotester. + +1. Clone the [markus-autotesting repo](https://github.com/MarkUsProject/markus-autotesting). Don't clone it into your `Markus` folder; we recommend cloning it into the same parent folder as your `Markus` folder. +2. `cd` into the `markus-autotesting` folder. +3. Run `docker-compose build` to build a new Docker images for the MarkUs autotester. +4. Run `docker-compose up` to create the new containers. The first time you run this it will take a long time because it'll install all of the MarkUs autotester's dependencies. + You'll know it's done when you see "INFO success..." +5. Stop the containers by pressing Ctrl + C (Windows/Linux) or Cmd + C (macOS). Then, restart the containers by running the command `docker-compose start`. +6. Leave the previous command running, and open a new terminal window. `cd` into your `Markus` folder and run `docker-compose run --rm rails rails db:autotest` (`rails` is written twice!). This should create sample autotesting assignments. + + TODO: show sample output + + **Troubleshooting**: if you see an "Unauthorized" error when running this step, you likely have an outdated autotester api key. Run the following command to delete it, and then try again: + + ```console + $ docker-compose run --rm rails rm config/autotest.api_key + ``` + +7. Start the MarkUs server: `docker-compose up --no-recreate rails`. +8. In a web browser, visit the running server, but using a different domain than `localhost`: + + - For Windows, first open a WSL terminal and enter the command `ip addr show eth0 | grep inet`. Use the IP address found after `inet`, which is a sequence of 4 numbers separated by `.`, e.g. `100.20.200.2`. The URL you should enter in your web browser is `:3000/csc108`. + - For macOS, visit `host.docker.internal:3000/csc108`. + - For Linux, visit `172.17.0.1:3000/csc108`. +9. Navigate to the `autotest_custom` assignment (under the Assignments tab), and go to Settings -> Automated Testing. This will take you to the settings page for the automated tests. +10. On that page, change the "Timeout" field from 30 to 60, and press "Save" at the bottom of the page. You should see a message at the top of the page that shows the status of updating the settings; wait until this message changes to "Completed". +11. Now go to the "Submissions" tab and click on the `aaaautotest` link in the leftmost column of the table. This takes you to the grading view for the submission. +12. Go to the Test Results tab and click on "Run Tests". +13. Wait a minute, and then refresh the page. Go back to the Test Results tab. You should see that two tests have been run, and that both have passed. diff --git a/Developer-Guide--Set-Up-With-Vagrant.md b/Developer-Guide--Set-Up-With-Vagrant.md new file mode 100644 index 0000000..88afee7 --- /dev/null +++ b/Developer-Guide--Set-Up-With-Vagrant.md @@ -0,0 +1,119 @@ +## Downloading and Installing + +If you want to get started on working on MarkUs quickly and painlessly, this is the way to do it. + +1. Install [VirtualBox](https://www.virtualbox.org/) and [Vagrant](http://www.vagrantup.com/) +2. Clone the Markus repo from GitHub by following the instructions in [Setting up Git and MarkUs](Developer-Guide--Setting-up-Git.md). (This is a document you will want to read very carefully and may come back to.) +3. `cd` to the repo (make sure you’re in the right directory - it should contain the Vagrantfile) +4. `vagrant up` + +This will download a fairly large (3GB) Debian box from the internet, so go [take a walk](http://news.stanford.edu/news/2014/april/walking-vs-sitting-042414.html) or something. This box has GNOME, PostgreSQL, git, and all of MarkUs’s other dependencies installed. When the download is complete, VirtualBox will run the box in headless mode. + +**NOTE:** If, for some reason, it fails and complains about SSH, you most likely have timed out. Check your internet connection attempt to limit network activity to `vagrant up`. + + + +## Connecting to your box + +Next, run `vagrant ssh` to connect to the virtual machine. (If it asks you for a password for vagrant, the password is "vagrant".) To avoid having to enter a password each time, and to use RubyMine, [set up](https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys--2) a public private key pair, and copy the public key to `~/.ssh/authorized_keys` on the vagrant vm. Then open the VagrantFile on your local machine and add `config.ssh.private_key_path = "ABSOLUTE_PATH"` directly under `config.vm.box = markusproject/ubuntu`. ABSOLUTE_PATH is the path to your private key (E.g. `$HOME/.ssh/id_rsa`). + +Note: On Windows you may find that you need to put the private key in the same directory as the Vagrantfile. + +**NOTE:** It is possible to set up the virtual machine to share folders with the host machine, but in our experience, this is too slow to be a good work environment, and sometimes doesn't work at all. If you do want to enable shared folders, you can check out that [vagrant documentation](http://docs.vagrantup.com/v2/synced-folders/). We have found it more effective to work with files locally using RubyMine and deploy/upload to the vagrant box when you want to try things out. + +Finally, run `markus` from the project directory. + +You should now be able to access the site from your host machine's browser at `http://0.0.0.0:3000/csc108`. + +The default admin user is `a` with any non-empty password. Look at `db/seeds.rb` for other users. + +If you are using RubyMine then you should jump down to the set up instructions for RubyMine below before proceeding to the next step. + + +## Using RubyMine + +1. Install [RubyMine](https://www.jetbrains.com/ruby/), and then run it. +2. When RubyMine runs, select `Open`, or `File > Open`, and navigate to your cloned MarkUs folder on your local machine. + + **NOTE**: RubyMine will tell you that there are missing gems to be installed, it is okay to ignore this. +3. Open `File > Settings` (on Windows) or `RubyMine > Preferences` (on OSX) where we will configure some different settings. + + 1) In `Tools > Vagrant`, set the *Instance folder* to your Markus directory on your local machine and leave the *Provider* as "Default". + + 2) Go to `Languages & Frameworks > Ruby SDK and Gems`, and click add symbol (plus sign) and select "New remote...". There are two ways to set up RubyMine to use the Ruby installed on the Vagrant machine. + + a) Select the Vagrant radio button, set the instance folder to the root MarkUs folder where the Vagrantfile is. + Confirm the connection works by clicking on the Host URL. + + b) If (a) does not work, then select the SSH Credentials radio button and enter the following: + + ``` + Host: localhost (**NOTE:** Windows may fail if you use 127.0.0.1, try using 'localhost' first before 127.0.0.1) + Port: 2222 + User name: vagrant + Auth type: Password + Password: vagrant, the password checkbox is selected + Ruby interpreter path: /usr/bin/ruby + ``` + + Now return to the Ruby SDK and Gems window and make sure the ruby you selected is installed. + + 3) Click `OK` to save your settings and close the window. + +3. You can open an ssh session to the vagrant virtual machine directly in RubyMine from `Tools > Start SSH Session`. You need to make sure that you have installed a public key on the vagrant machine so that you don't need a password (or passphrase) to ssh into the vagrant VM. + +At this point, you may need to restart RubyMine before making the next step work. There also is an option to do SSH through RubyMine and start/pause/kill Vagrant if you have not done so before starting RubyMine. These commands can be found under `Tools > Vagrant`. You may need the Vagrant machine to be running for the next step: + +4. In RubyMine, select `Tools > Deployment > Configuration`, and click the + in the top left to add a new server. After giving it a name, under `Connection` use the following settings: + + ``` + Type: SFTP + Host: localhost + Port: 2222 + User name: vagrant + Authentication: password + Password: vagrant + Root path: /home/vagrant + ``` + +5. In the same window, under `Mappings` set: + + ``` + Local path: [path to your local Markus repo] + Deployment path on server: /Markus + ``` + +6. In the same window under `Excluded Paths`, add the following sets of paths. + + **Deployment paths**: + - /Markus/.bundle + - /Markus/.byebug_history + - /Markus/config/database.yml + - /Markus/data/dev + - /Markus/log + - /Markus/node_modules + - /Markus/public/javascripts + - /Markus/public/packs + - /Markus/public/packs-test + - /Markus/vendor/bundle + - /Markus/lib/scanner/venv + + **Local paths** (MARKUS_ROOT is the location of your MarkUs repo): + - MARKUS_ROOT/.vagrant + - MARKUS_ROOT/config/dummy_validate.sh + +7. Click `OK` to save your changes and close the window. + +8. Select `Tools > Options`, and set: + + - "Delete target items when source ones do not exist..." should be **checked**. + - "Upload changed files automatically to the default server..." should be **On explicit save action**. + - "Skip external changes" should be **NOT checked**. + + Click OK to save your changes. + +9. To test out this configuration, first right-click on the Markus folder in the "Project" pane, and select `Deployment > Upload to vagrant`. This should take a bit of time, as your files are copied from your local machine to the virtual machine. + + Whenever you make changes to the files, you can do an explicit save action and *all* of your changes will be uploaded to the virtual machine. + +Congratulations, you're all done and ready to get started working on MarkUs! diff --git a/GitHowTo.md b/Developer-Guide--Setting-up-Git.md similarity index 88% rename from GitHowTo.md rename to Developer-Guide--Setting-up-Git.md index f6da377..2d739f6 100644 --- a/GitHowTo.md +++ b/Developer-Guide--Setting-up-Git.md @@ -1,16 +1,22 @@ -MarkUs, Git and GitHub: How it Works -==================================== +# MarkUs, Git and GitHub: How it Works -Overview --------- +## Installation -![](images/markus-git-workflow.png "") +First, you'll need to [install Git](https://git-scm.com/downloads) onto your computer. +If you already have Git installed, we recommend [updating to the latest version](https://confluence.atlassian.com/bitbucketserver/installing-and-upgrading-git-776640906.html). + +**Important**: if you are developing MarkUs on Windows, you should be using WSL to manage your repository, which means you'll need to [Install Git on WSL](https://docs.microsoft.com/en-us/windows/wsl/tutorials/wsl-git) instead of on Windows directly. +All of the commands on this page should be run in the WSL 2 terminal (e.g., Ubuntu 20.04), not the regular Windows terminal (cmd.exe). + +## Overview + +![Workflow](images/markus-git-workflow.png) Note that the following few paragraphs might be a bit confusing. Hang in there, there are always people around you can ask. ### Setting up MarkUs -1. Open a GitHub account and set up an [SSH public key for Git](https://help.github.com/articles/generating-ssh-keys)\_ +1. Open a GitHub account and set up an [SSH public key for Git](https://help.github.com/articles/generating-ssh-keys) 2. Set up your Git configuration settings. $ git config --global user.name "First-name Last-name" @@ -19,7 +25,7 @@ Note that the following few paragraphs might be a bit confusing. Hang in there, You may omit the `--global` switch if you wish. Make sure to read up on the differences between global and non-global git configuration, though. Thanks. 3. Ask an Admin to add you as a MarkUs developer. -4. Visit the [MarkUs GitHub repository](https://github.com/MarkUsProject/Markus)\_ and press “Fork”. This will create a clone of the repository for your GitHub account. +4. Visit the [MarkUs GitHub repository](https://github.com/MarkUsProject/Markus) and press “Fork”. This will create a clone of the repository for your GitHub account. 5. From your fork, copy the URL that allows you to access it using SSH. In the terminal, navigate to your intended local development directory, and run the following command using the URL you just copied. @@ -145,9 +151,9 @@ Git Tricks - You might want to see who modified a line last, and what other changes they brought in with that commit. - $ git blame config/routes.rb + $ git blame config/routes.rb - You can also use the GitHub interface for this by clicking "Blame" when viewing a file, which will take you to a page like [this](https://github.com/MarkUsProject/Markus/blame/master/config/routes.rb)\_. + You can also use the GitHub interface for this by clicking "Blame" when viewing a file, which will take you to a page like [this](https://github.com/MarkUsProject/Markus/blame/master/config/routes.rb). ### Issues & Solutions @@ -188,4 +194,4 @@ Git Tricks > > > master - Finally, switch over to the new branch and get to work: `git checkout issue-1234`. \ No newline at end of file + Finally, switch over to the new branch and get to work: `git checkout issue-1234`. diff --git a/Annotations.md b/Developer-Guide--Tips-And-Tricks--Annotations.md similarity index 79% rename from Annotations.md rename to Developer-Guide--Tips-And-Tricks--Annotations.md index 37b6bd4..9da0aba 100644 --- a/Annotations.md +++ b/Developer-Guide--Tips-And-Tricks--Annotations.md @@ -1,14 +1,10 @@ -Annotations -=========== +# Annotations -When source code is graded by a TA, or viewed later on by the student that submitted it, the code may have one or more "annotations" from the TA. +When submitted work is graded by a TA, or viewed later on by the student that submitted it, each submitted file may have one or more *annotations* from the TA. (We use the term "annotations" to distinguish that idea from *comments*, since students could have lots of comments in their code that a TA would want to annotate.) -**Note:** We use the term annotations, so as to distinguish that idea from *comments*, since students could have lots of *comments* in their code that a TA would want to annotate. +Here's a user story that describes what an annotation more or less is. -Here's a user story that describes what an annotation more or less is: - -What is an Annotation? A User Story... --------------------------------------- +## What is an Annotation? A User Story... Jamie, the TA, is about to grade some C code submitted by c9doej. Jamie pulls up the student source code, and begins to read it through it. Jamie is pleased at how easy the code is to read, because it is syntax highlighted. @@ -16,67 +12,38 @@ Part way down, Jamie notices that c9doej forgot to free some memory that had bee "You should have freed variable x here. This is a memory leak!" -Jamie presses the submit button, and the lines that Jamie had highlighted glows a different colour than the rest of the source code. When Jamie moves the mouse cursor over the glowing lines, a little box pops up to display Jamie's message. +Jamie presses the submit button, and the lines that Jamie had highlighted glows a different colour than the rest of the source code. When Jamie moves the mouse cursor over the glowing code, a little box pops up to display Jamie's message. Later on, when all of the assignments have been marked and returned, c9doej logs in to check his grade. Scanning through his code, he sees the glowing lines. He hovers his mouse over the lines and reads Jamie's message. "Of course!", thinks c9doej, "I knew that. Won't make that mistake twice." -The end. - -Annotations: The Rules ----------------------- - -### Syntax Highlighting - -- Source code to be graded or viewed must be syntax highlighted. As of this writing, we are using [[Syntax Highlighter 1.5.1 | http://code.google.com/p/syntaxhighlighter/]] to do the job. - -- Annotations are applied to *lines* of source code, not characters within those lines. This is different from the original OLM, and is due to the new syntax highlighting feature +## Annotations: The Rules -### Canned Annotations +1. Annotations can be applied to any submitted file type (plaintext, PDF, and images). +2. For plaintext files, annotations are applied on top of the syntax highlighting of the code. We are currently using [Syntax Highlighter 1.5.1](https://github.com/syntaxhighlighter/syntaxhighlighter) for syntax highlighting. +3. Annotations can be removed by after they've been added. +4. Annotations can also be edited. However, if a TA edits a "reusable" annotation (see below), this will change that annotation for all places where it was used added. -- Annotations can be "canned" annotations. This means that there might be an annotation that keeps being added to student source code again and again -for example, if students are consistently writing code lines longer than 80 characters. Since TA's don't want to have to rewrite this annotation each time, this annotation should be available to be applied to source code from a list. +### Common/Reusable Annotations -- Each "canned" annotation belongs to its own category. For example, an annotation talking about long code lines would probably be put under a category called "Style". - -- Annotation categories persist across a single assignment. For example, for Assignment 1, the same "canned" annotations would exist for the TA to apply to student code. However, for Assignment 2, new annotations must be created and used. +Annotations can be *common* or *reusable*. This is for when an annotation is used for several different submissions or several places in the same submission, for example, if students are consistently writing code lines longer than 80 characters. +Each reusable annotation belongs to an *annotation category*. For example, an annotation talking about long code lines would probably be put under a category called "Style". +Each assignment has its own annotation categories. ### On-the-fly Annotations -- Annotations can also be added on-the-fly for a particular student submission. For example, if c9doej submits some code where a few lines are completely unreadable, the TA might write a special annotation just for that student. - -- When TA's press the "create new annotation" button, a dialog comes up for the annotation text. This dialog will also ask them which category they would like to add this annotation to if they want it to be "canned". The default is to leave the annotation category as "uncategorized", meaning that it's an on-the-fly annotation not meant to be added to multiple students. - -### Editing and Removal - -- Annotations can be removed from source code by TA's after they've been added. - -- Annotations can also be edited by TA's. However, if a TA edits a "canned" annotation, this will change that annotation for all places where that annotation has been added. - -Implementation --------------- - -### Database / Server Side - -There are three database tables that deal with annotations: Annotation Categories, Annotation Labels, and Annotations. - -#### Annotation Categories - -- Belongs to an Assignment -- Has Many Annotation Labels -- Has a name, like "Style" or "Memory Management" - -#### Annotation Labels - -- Contains the actual text content of any annotation +Annotations can also be added on-the-fly for a particular submission. For example, if c9doej submits some code where a few lines are completely unreadable, the TA might write a unique annotation just for that student. +When TAs press the "Create New Annotation" button, a dialog comes up for the annotation text. This dialog will also ask them which category they would like to add this annotation to if they want it to be "canned". The default is to leave the annotation category as "uncategorized", meaning that it's an on-the-fly annotation not meant to be added to multiple students. -- Has a column for Annotation Category ID: if NOT NULL, then this is a "canned" annotation label that can be applied across many submission files. If Annotation Category ID *is* NULL, then this is an on-the-fly "uncategorized" Annotation Label, meant only to be put on a single submission file. -- Belongs to an Annotation Category +## Implementation Details -### Annotations +### Models -- This is the table that links submission files with annotation labels on particular line numbers (line\_start and line\_end of a Submission File) +There are three models that deal with annotations: `AnnotationCategory`, `AnnotationText`, and `Annotation`. -- Belongs to a Submission File and an Annotation Label +- `AnnotationCategory`: a group of reusable annotations. It belongs to an `Assignment`, and has many `AnnotationText`s. It has a name for the category, like `'Style'` or `'Memory Management'`. Only admins can create annotation categories. +- `AnnotationText` contains the actual text content of an annotation. Each `AnnotationText` optionally belongs to an `AnnotationCategory`; if the association is `nil`, this text is an on-the-fly annotation, used for just a single submission file. +- `Annotation`: an actual annotation given by a TA for a submission. It belongs to a `SubmissionFile` *and* `AnnotationText`, and also contains information about where the annotation occurs. These "location" columns are all nullable because they only apply to certain kinds of submission files. ### JavaScript / Client Side @@ -256,4 +223,4 @@ Finally, the SourceCodeLineAnnotations object is created using the SourceCodeLin line\_annotations is called and manipulated by basic Javascript functions: add\_annotation\_label, add\_annotation, remove\_annotation, update\_annotation\_label. -And that's how the annotations more or less work. \ No newline at end of file +And that's how the annotations more or less work. diff --git a/Developer-Guide--Tips-And-Tricks--Enabling-ActionMailer-In-Development.md b/Developer-Guide--Tips-And-Tricks--Enabling-ActionMailer-In-Development.md new file mode 100644 index 0000000..3555912 --- /dev/null +++ b/Developer-Guide--Tips-And-Tricks--Enabling-ActionMailer-In-Development.md @@ -0,0 +1,31 @@ +# Action Mailer in MarkUs + +The Rails ActionMailer is used in MarkUs to send automated emails when grouping submissions are released, grade entry forms are released, and students invite other students to groupings. These calls to the mailer are in the files `submissions_helper.rb`, `grade_entry_forms_controller.rb`, and `groups_controller.rb` respectively. Students opt in to receiving emails by default, but can choose to unsubscribe by changing their preferences through two attributes: `receives_results_emails` and `receives_invite_emails`. + +# Changing the Development Environment + +When using the Action Mailer features of MarkUs to send email notifications to students, the MarkUs administrators configure an email server. For the sake of development, you can configure MarkUs to send emails from a service you already use (such as Gmail, Outlook, etc). You can do so by changing the development environment with the `config.action_mailer` values. As an example, consider the case we want to send an email, and we can use our gmail account to do so. We `cd` into the Markus repository and go to `/config/environments/development.rb` and add the following lines after the section on logging (replacing your email and password in the relevant fields indicated). + +``` + ################################################################### + # Email Notifications + ################################################################### + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { + address: 'smtp.gmail.com', + port: 587, + domain: 'example.com', + user_name: '', + password: '', + authentication: 'plain', + enable_starttls_auto: true + } + config.action_mailer.default_url_options = {host: 'localhost:3000'} + config.action_mailer.asset_host = 'http://localhost:3000' + config.action_mailer.perform_deliveries = true +``` +**Remember to never commit your email and password.** +# For Troubleshooting Email Issues +If you have issues with emails not sending due to authentication issues (in particular gmail), you can reference the `Troubleshooting Email Sending Errors` section in [this guide.](https://dev.to/morinoko/sending-emails-in-rails-with-action-mailer-and-gmail-35g4) + +In the case that your issue is not solved by that information, head to [the Action Mailer guide.](https://guides.rubyonrails.org/action_mailer_basics.html) diff --git a/Developer-Guide--Tips-And-Tricks--Groups-Groupings-And-Repositories.md b/Developer-Guide--Tips-And-Tricks--Groups-Groupings-And-Repositories.md new file mode 100644 index 0000000..9947a1e --- /dev/null +++ b/Developer-Guide--Tips-And-Tricks--Groups-Groupings-And-Repositories.md @@ -0,0 +1,23 @@ +# Groups, Groupings and Repositories + +## Assignment Group Models + +There are two models that MarkUs supports for managing group membership across multiple assignments in a course. + +- Model 1: Assignment groups are independent of one another. Students may freely switch groups from one assignment to the next. +- Model 2: The same group submits work for multiple assignments. For example, a large course project is split across multiple assignments, and the same group submits work for each assignment. + +## Groups vs. Groupings + +A **grouping** is a set of students who are working together on a single assignment in MarkUs. +A **group** is a collection of groupings, and is used to automatically persist students groups across different assignments if the courses uses "Model 2" above. + +Every *group* has an associated repository which stores the files that have been submitted for all of its groupings. Because each repository is associated with a group and not a grouping, one repository may store submitted files for multiple assignments, if the group was persisted across those assignments. This makes it more convenient for students working on a course project split across multiple assignments, as they can keep using the same group repository to store their work. + +## Individual Groups + +To keep things uniform, MarkUs always associates submitted files with a grouping rather than an individual student, even when that student worked individually for an assignment. It is possible to have a group containing just one student member. + +A special case is when the assignment is configured so that students cannot work in groups. In this case, each student is part of a grouping (where they are the only member), but the associated group is the student's "individual group", whose name is the same as the student's username. This means that for all individual assignments, the student groupings are associated with the same individual group, and hence use the same individual repository. Again, this is for the convenience of the student. + +For an assignment that allows group work, instructors may still allow students to work individually if they set the assignment's minimum group size to 1. In this case, students have the option of "Working Alone", in which case their grouping will be associated with the individual group and repository, rather than creating a new group. diff --git a/SecurityTesting.md b/Developer-Guide--Tips-And-Tricks--Security.md similarity index 59% rename from SecurityTesting.md rename to Developer-Guide--Tips-And-Tricks--Security.md index 51e6239..d0af7a5 100644 --- a/SecurityTesting.md +++ b/Developer-Guide--Tips-And-Tricks--Security.md @@ -1,87 +1,203 @@ -Security Testing Guidelines -=========================== +# Security -These guidelines are based on the [OWASP Top Ten Web Application Vulnerabilities](http://www.owasp.org/index.php/Top_10_2007)\_. Individual descriptions are followed by a link to the appropriate expanded reference page on the OWASP website. +## About -A1 - Cross Site Scripting (XSS) -------------------------------- +When developing code for MarkUs, a priority should always be to make the code as secure as possible. As a Ruby on Rails application, MarkUs uses a lot of the built-in security features that Rails offers. Whenever developing code for MarkUs, please read and follow the [Securing Rails Applications guide](https://edgeguides.rubyonrails.org/security.html). -*XSS flaws occur whenever an application takes user supplied data and sends it to a web browser without first validating or encoding that content. XSS allows attackers to execute script in the victim's browser which can hijack user sessions, deface web sites, possibly introduce worms, etc.* ([link](http://www.owasp.org/index.php/Top_10_2007-Cross_Site_Scripting)\_) +## Developing with Security in Mind + +Please see below for MarkUs specific security approaches: + +### Cross-Site Request Forgery (CSRF) + +- Make sure that `protect_from_forgery with: exception` is set for all routes and the `csrf_meta_tags` are set for all views. + +### File Upload + +- All files uploaded by users should have their filename and filepath sanitized so that files are only written to disk in approved locations. +- Files should be written to disk with minimal permissions (ie. do not make uploaded files executable!) + +### Input Validation + +- User input (eg. form fields) should always be validated in both the browser and again when the request is received by the server. +- In the browser, use [client-side form validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation). +- Use validations on model attributes on the server side. In some cases, additional input validation may be required in controller methods as well. + +### Content Security Policy (CSP) Headers + +MarkUs uses content security policies (CSP) help prevent XSS and CSS injection attacks and are defined in `content_security_policy.rb` initializer file using Rails' [CSP DSL](https://edgeguides.rubyonrails.org/security.html#content-security-policy). + +In some cases, MarkUs depends on external code that violates it's CSP. In these cases, it is may be necessary to temporarily override these policies for specific routes. Avoid this if at all possible by using dependencies that do not violate the CSPs outlined in `content_security_policy.rb` but if you absolutely have to: + +- Only change the policies for the routes that require it. For example, do not change a policy for all routes in a controller if the change is only necessary for one or two routes. +- Change the policies so that they ensure the maximum amount of protection. For example, do not permit content from all https sources if only content from a single url is required. + +#### Writing code to comply with CSP + +MarkUs' CSP do not permit certain elements to appear in the DOM as they pose a security risk. Please see below for alternative (more secure) ways of writing html and js code: + +- Do not include inline style attributes in DOM elements. Make sure all styling is done in css files: + +``` +// DO NOT DO THIS +