{ "info": { "author": "Dmitry Viskov", "author_email": "dmitry.viskov@webenterprise.ru", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 1.11", "Framework :: Django :: 2.0", "Framework :: Django :: 2.1", "Framework :: Django :: 2.2", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Security", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "LTI 1.3 Advantage Tool implementation in Python\n===============================================\n\n.. image:: https://img.shields.io/pypi/v/PyLTI1p3\n :scale: 100%\n :target: https://pypi.python.org/pypi/PyLTI1p3\n :alt: PyPI\n\n.. image:: https://img.shields.io/pypi/pyversions/PyLTI1p3\n :scale: 100%\n :target: https://www.python.org/\n :alt: Python\n\n.. image:: https://travis-ci.org/dmitry-viskov/pylti1.3.svg?branch=master\n :scale: 100%\n :target: https://travis-ci.org/dmitry-viskov/pylti1.3\n :alt: Build Status\n\n.. image:: https://img.shields.io/github/license/dmitry-viskov/pylti1.3\n :scale: 100%\n :target: https://raw.githubusercontent.com/dmitry-viskov/pylti1.3/master/LICENSE\n :alt: MIT\n\n\nThis project is a Python implementation of the similar `PHP tool`_.\nLibrary contains adapter for usage from Django Web Framework but there is no difficult to adapt it to from other frameworks: you should just re-implement ``OIDCLogin`` and ``MessageLaunch`` classes as it already done for Django.\n\n.. _PHP tool: https://github.com/IMSGlobal/lti-1-3-php-library\n\nExample\n=======\n\nFirst of all choose and configure test LTI 1.3 Platform. It may be:\n\n* `IMS Global test site`_\n* `Canvas LMS`_ (Detailed `instruction`_ how to setup Canvas as LTI 1.3 Platform is `here`_)\n\n.. _IMS Global test site: https://lti-ri.imsglobal.org\n.. _Canvas LMS: https://github.com/instructure/canvas-lms\n.. _instruction: https://github.com/dmitry-viskov/pylti1.3/wiki/Configure-Canvas-as-LTI-1.3-Platform\n.. _here: https://github.com/dmitry-viskov/pylti1.3/wiki/Configure-Canvas-as-LTI-1.3-Platform\n\nThe most simple way to check example is to use ``docker`` + ``docker-compose``.\nChange the necessary configs in the ``examples/configs/game.json`` (`here is instruction`_ how to generate your own public + private keys):\n\n.. _here is instruction: https://github.com/dmitry-viskov/pylti1.3/wiki/How-to-generate-JWT-RS256-key-and-JWKS\n\n.. code-block:: javascript\n\n {\n \"\" : { // This will usually look something like 'http://example.com'\n \"client_id\" : \"\", // This is the id received in the 'aud' during a launch\n \"auth_login_url\" : \"\", // The platform's OIDC login endpoint\n \"auth_token_url\" : \"\", // The platform's service authorization endpoint\n \"key_set_url\" : \"\", // The platform's JWKS endpoint\n \"key_set\": null, // in case if platform's JWKS endpoint somehow unavailable you may paste JWKS here\n \"private_key_file\" : \"\", // Relative path to the tool's private key\n \"deployment_ids\" : [\"\"] // The deployment_id passed by the platform during launch\n }\n }\n\nand execute:\n\n.. code-block:: shell\n\n $ docker-compose up --build\n\nYou may use virtualenv instead of docker:\n\n.. code-block:: shell\n\n $ virtualenv venv\n $ source venv/bin/activate\n $ cd examples\n $ pip install -r requirements.txt\n $ cd game\n $ python manage.py runserver 127.0.0.1:9001\n\nNow there is game example tool you can launch into on the port 9001:\n\n.. code-block:: shell\n\n OIDC Login URL: http://127.0.0.1:9001/login/\n LTI Launch URL: http://127.0.0.1:9001/launch/\n\nConfiguration & Usage\n=====================\n\nAccessing Registration Data\n---------------------------\n\nTo configure your own tool you may use built-in adapters:\n\n.. code-block:: python\n\n from pylti1p3.tool_config import\n tool_conf = ToolConfJsonFile('path/to/json')\n\n from pylti1p3.tool_config import ToolConfDict\n settings = {\n \"\": { },\n \"\": { }\n }\n private_key = '...'\n tool_conf = ToolConfDict(settings)\n tool_conf.set_private_key(iss, private_key)\n\nor create your own implementation. The ``pylti1p3.tool_config.ToolConfAbstract`` interface must be fully implemented for this to work.\n\nOpen Id Connect Login Request\n-----------------------------\n\nLTI 1.3 uses a modified version of the OpenId Connect third party initiate login flow. This means that to do an LTI 1.3 launch, you must first receive a login initialization request and return to the platform.\n\nTo handle this request, you must first create a new ``OIDCLogin`` (or ``DjangoOIDCLogin``) object:\n\n.. code-block:: python\n\n from pylti1p3.contrib.django import DjangoOIDCLogin\n\n oidc_login = DjangoOIDCLogin(request, tool_conf)\n\nNow you must configure your login request with a return url (this must be preconfigured and white-listed on the tool).\nIf a redirect url is not given or the registration does not exist an ``pylti1p3.exception.OIDC_Exception`` will be thrown.\n\n.. code-block:: python\n\n try:\n oidc_login.redirect(get_launch_url(request))\n except OIDC_Exception:\n # display error page\n log.error('Error doing OIDC login')\n\nWith the redirect, we can now redirect the user back to the tool.\nThere are three ways to do this:\n\nThis will add a HTTP 302 location header:\n\n.. code-block:: python\n\n oidc_login.redirect(get_launch_url(request))\n\nThis will display some javascript to do the redirect instead of using a HTTP 302:\n\n.. code-block:: python\n\n oidc_login.redirect(get_launch_url(request), js_redirect=True)\n\nYou can also get the url you need to redirect to, with all the necessary query parameters (if you would prefer to redirect in a custom way):\n\n.. code-block:: python\n\n redirect_obj = oidc_login.get_redirect_object()\n redirect_url = redirect_obj.get_redirect_url()\n\nRedirect is done, we can move onto the launch.\n\nLTI Message Launches\n--------------------\n\nNow that we have done the OIDC log the platform will launch back to the tool. To handle this request, first we need to create a new ``MessageLaunch`` (or ``DjangoMessageLaunch``) object.\n\n.. code-block:: python\n\n message_launch = DjangoMessageLaunch(request, tool_conf)\n\nOnce we have the message launch, we can validate it. Validation is transparent - it's done once before you try to access the message body:\n\n.. code-block:: python\n\n try:\n launch_data = message_launch.get_launch_data()\n except LtiException:\n log.error('Launch validation failed')\n\nYou may do it more explicitly:\n\n.. code-block:: python\n\n try:\n launch_data = message_launch.set_auto_validation(enable=False).validate()\n except LtiException:\n log.error('Launch validation failed')\n\nNow we know the launch is valid we can find out more information about the launch.\n\nCheck if we have a resource launch or a deep linking launch:\n\n.. code-block:: python\n\n if message_launch.is_resource_launch():\n # Resource Launch!\n elif message_launch.is_deep_link_launch():\n # Deep Linking Launch!\n else:\n # Unknown launch type\n\nCheck which services we have access to:\n\n.. code-block:: python\n\n if message_launch.has_ags():\n # Has Assignments and Grades Service\n if message_launch.has_nrps():\n # Has Names and Roles Service\n\nAccessing Cached Launch Requests\n--------------------------------\n\nIt is likely that you will want to refer back to a launch later during subsequent requests. This is done using the launch id to identify a cached request. The launch id can be found using:\n\n.. code-block:: python\n\n launch_id = message_launch.get_launch_id()\n\nOnce you have the launch id, you can link it to your session and pass it along as a query parameter.\n\n**Make sure you check the launch id against the user session to prevent someone from making actions on another person's launch.**\n\nRetrieving a launch using the launch id can be done using:\n\n.. code-block:: python\n\n message_launch = DjangoMessageLaunch.from_cache(launch_id, request, tool_conf)\n\nOnce retrieved, you can call any of the methods on the launch object as normal, e.g.\n\n.. code-block:: python\n\n if message_launch.has_ags():\n # Has Assignments and Grades Service\n\nDeep Linking Responses\n----------------------\n\nIf you receive a deep linking launch, it is very likely that you are going to want to respond to the deep linking request with resources for the platform.\n\nTo create a deep link response you will need to get the deep link for the current launch:\n\n.. code-block:: python\n\n deep_link = message_launch.get_deep_link()\n\nNow we need to create ``pylti1p3.deep_link_resource.DeepLinkResource`` to return:\n\n.. code-block:: python\n\n resource = DeepLinkResource()\n resource.set_url(\"https://my.tool/launch\")\\\n .set_custom_params({'my_param': my_param})\\\n .set_title('My Resource')\n\nEverything is set to return the resource to the platform. There are two methods of doing this.\n\nThe following method will output the html for an aut-posting form for you.\n\n.. code-block:: python\n\n deep_link.output_response_form([resource1, resource2])\n\nAlternatively you can just request the signed JWT that will need posting back to the platform by calling.\n\n.. code-block:: python\n\n deep_link.get_response_jwt([resource1, resource2])\n\nNames and Roles Service\n-----------------------\n\nBefore using names and roles you should check that you have access to it:\n\n.. code-block:: python\n\n if not message_launch.has_nrps():\n raise Exception(\"Don't have names and roles!\")\n\nOnce we know we can access it, we can get an instance of the service from the launch.\n\n.. code-block:: python\n\n nrps = message_launch.get_nrps()\n\nFrom the service we can get list of all members by calling:\n\n.. code-block:: python\n\n members = nrps.get_members()\n\nAssignments and Grades Service\n------------------------------\n\nBefore using assignments and grades you should check that you have access to it:\n\n.. code-block:: python\n\n if not launch.has_ags():\n raise Exception(\"Don't have assignments and grades!\")\n\nOnce we know we can access it, we can get an instance of the service from the launch:\n\n.. code-block:: python\n\n ags = launch.get_ags()\n\nTo pass a grade back to the platform, you will need to create a ``pylti1p3.grade.Grade`` object and populate it with the necessary information:\n\n.. code-block:: python\n\n gr = Grade()\n gr.set_score_given(earned_score)\\\n .set_score_maximum(100)\\\n .set_timestamp(datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+0000'))\\\n .set_activity_progress('Completed')\\\n .set_grading_progress('FullyGraded')\\\n .set_user_id(external_user_id)\n\nTo send the grade to the platform we can call:\n\n.. code-block:: python\n\n ags.put_grade(gr)\n\nThis will put the grade into the default provided lineitem. If no default lineitem exists it will create one.\n\nIf you want to send multiple types of grade back, that can be done by specifying a ``pylti1p3.lineitem.LineItem``:\n\n.. code-block:: python\n\n line_item = LineItem()\n line_item.set_tag('grade')\\\n .set_score_maximum(100)\\\n .set_label('Grade')\n\n ags.put_grade(gr, line_item);\n\nIf a lineitem with the same ``tag`` exists, that lineitem will be used, otherwise a new lineitem will be created.\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/dmitry-viskov/pylti1.3", "keywords": "pylti,pylti1p3,lti,lti1.3,lti1p3,django", "license": "MIT", "maintainer": "Dmitry Viskov", "maintainer_email": "", "name": "PyLTI1p3", "package_url": "https://pypi.org/project/PyLTI1p3/", "platform": "", "project_url": "https://pypi.org/project/PyLTI1p3/", "project_urls": { "Homepage": "https://github.com/dmitry-viskov/pylti1.3" }, "release_url": "https://pypi.org/project/PyLTI1p3/1.3.0/", "requires_dist": [ "pyjwt (>=1.5)", "jwcrypto", "requests" ], "requires_python": "", "summary": "LTI 1.3 Advantage Tool implementation in Python", "version": "1.3.0" }, "last_serial": 5715177, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "223b9bb9df840164c6dd04868f5a866b", "sha256": "cf1573e7d70d27094cd4f550e02a770b1590d55f012ab27c4f3bee700fd48e36" }, "downloads": -1, "filename": "PyLTI1p3-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "223b9bb9df840164c6dd04868f5a866b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 24798, "upload_time": "2019-08-13T11:23:05", "url": "https://files.pythonhosted.org/packages/14/05/9403b3e5aee6103c0493e6d8a60bfa8308ff6dae4990ce25658a0980800b/PyLTI1p3-1.0.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2d89f903f24153ca7627652978ac49d0", "sha256": "ca94ffa611bfc19c7a55a742b2086e508d424d06c99d9319184b701f75060b16" }, "downloads": -1, "filename": "PyLTI1p3-1.0.0.tar.gz", "has_sig": false, "md5_digest": "2d89f903f24153ca7627652978ac49d0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20603, "upload_time": "2019-08-13T11:23:08", "url": "https://files.pythonhosted.org/packages/98/d4/adf9e8f414570023ff52e7e20c41789c4839c0d2d9eae8f3e3f8ef534880/PyLTI1p3-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "717e672bab7b2a579b3d1ad88e45d39e", "sha256": "349fe03405119fb6ee0415b78925dad8e54cd05f3ba227c4ba2564e7780bc547" }, "downloads": -1, "filename": "PyLTI1p3-1.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "717e672bab7b2a579b3d1ad88e45d39e", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 24959, "upload_time": "2019-08-16T13:15:55", "url": "https://files.pythonhosted.org/packages/80/02/7370d0555521620ccbfa1d3e6219328da7520af666581db79eace4e40583/PyLTI1p3-1.1.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "4aeb246cd68033e01fc12ade78680248", "sha256": "32124ed60360b1e5713c15e1565ff4acbde8e12040ca984c375e7a766f30edeb" }, "downloads": -1, "filename": "PyLTI1p3-1.1.0.tar.gz", "has_sig": false, "md5_digest": "4aeb246cd68033e01fc12ade78680248", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20748, "upload_time": "2019-08-16T13:15:58", "url": "https://files.pythonhosted.org/packages/0b/65/58054dc66cb8ee44e67df396b90971ee176c9f206c0ef04cff7333f34e28/PyLTI1p3-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "b0893ead9fbe63bda60da440198253c8", "sha256": "527fb69a68a69bcd8412f3c94ee2792e8f2696d4baf1f82365789ee558198569" }, "downloads": -1, "filename": "PyLTI1p3-1.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "b0893ead9fbe63bda60da440198253c8", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 24959, "upload_time": "2019-08-16T15:19:33", "url": "https://files.pythonhosted.org/packages/3b/47/07e21f9b73f517905da0959e2fb48275e7f422a832b257ac64ac401e9750/PyLTI1p3-1.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "0ce2825b504f3a9becbd2392be85d236", "sha256": "4b8f2c137399d1bfbe235fa9246266723358d434bf8acbd7c733b9ebd06c0cef" }, "downloads": -1, "filename": "PyLTI1p3-1.1.1.tar.gz", "has_sig": false, "md5_digest": "0ce2825b504f3a9becbd2392be85d236", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20747, "upload_time": "2019-08-16T15:19:37", "url": "https://files.pythonhosted.org/packages/28/7d/c840d2de0890327fe33f7e5d4dc360dd5951dc33e34584f54afa04c81be8/PyLTI1p3-1.1.1.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "69e44cdc0270d01a865f4f6de6b75890", "sha256": "877445187873ac22e49f116f27e3418b9ae0cd740f6b8f4f50bb7256de4d674b" }, "downloads": -1, "filename": "PyLTI1p3-1.2.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "69e44cdc0270d01a865f4f6de6b75890", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 25125, "upload_time": "2019-08-20T17:19:44", "url": "https://files.pythonhosted.org/packages/1d/22/3ed2fe943c2e6cf083b86caf07f2ce07da797a47ff5841642d9e0573e30c/PyLTI1p3-1.2.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "005fc4b4e26d436422163c57fc60d3a5", "sha256": "69f76b3244a7f7d9f058faed3f5b5e43d9d00dbd0bc0bc5b8bb35965a69dfb96" }, "downloads": -1, "filename": "PyLTI1p3-1.2.0.tar.gz", "has_sig": false, "md5_digest": "005fc4b4e26d436422163c57fc60d3a5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 20908, "upload_time": "2019-08-20T17:19:46", "url": "https://files.pythonhosted.org/packages/2e/31/c55428080a773447eb6d779bcc19e85773c74d49c9141ca2c899f9a0c38a/PyLTI1p3-1.2.0.tar.gz" } ], "1.3.0": [ { "comment_text": "", "digests": { "md5": "33ff593d5739a0e3d87376b3b275f5f8", "sha256": "82293182697148feee7686e3ea029357d3cae45884b06c354de82cc8148cc299" }, "downloads": -1, "filename": "PyLTI1p3-1.3.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "33ff593d5739a0e3d87376b3b275f5f8", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 25393, "upload_time": "2019-08-22T13:17:59", "url": "https://files.pythonhosted.org/packages/09/a1/2bc694313083cc46724ec26ff39fd04aaedd2ef7a9c748810af6d3021620/PyLTI1p3-1.3.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "40ca78935f5acc5d5312c15497f263d8", "sha256": "fadb860f4600b7f355435dbc5866445bd240a7df68740b45df3b2937d21cd7bb" }, "downloads": -1, "filename": "PyLTI1p3-1.3.0.tar.gz", "has_sig": false, "md5_digest": "40ca78935f5acc5d5312c15497f263d8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21419, "upload_time": "2019-08-22T13:18:01", "url": "https://files.pythonhosted.org/packages/85/3e/e3b1f07bde3eaebc4482ac5889349a59bdddc8ec011a3fcc04f8aa4b94e4/PyLTI1p3-1.3.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "33ff593d5739a0e3d87376b3b275f5f8", "sha256": "82293182697148feee7686e3ea029357d3cae45884b06c354de82cc8148cc299" }, "downloads": -1, "filename": "PyLTI1p3-1.3.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "33ff593d5739a0e3d87376b3b275f5f8", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 25393, "upload_time": "2019-08-22T13:17:59", "url": "https://files.pythonhosted.org/packages/09/a1/2bc694313083cc46724ec26ff39fd04aaedd2ef7a9c748810af6d3021620/PyLTI1p3-1.3.0-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "40ca78935f5acc5d5312c15497f263d8", "sha256": "fadb860f4600b7f355435dbc5866445bd240a7df68740b45df3b2937d21cd7bb" }, "downloads": -1, "filename": "PyLTI1p3-1.3.0.tar.gz", "has_sig": false, "md5_digest": "40ca78935f5acc5d5312c15497f263d8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 21419, "upload_time": "2019-08-22T13:18:01", "url": "https://files.pythonhosted.org/packages/85/3e/e3b1f07bde3eaebc4482ac5889349a59bdddc8ec011a3fcc04f8aa4b94e4/PyLTI1p3-1.3.0.tar.gz" } ] }