You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/10/06 13:41:24 UTC

[airavata-django-portal] branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo updated (26a192d -> 723cf77)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


 discard 26a192d  AIRAVATA-3319 Admin API for updating a user's username
 discard 77a3413  AIRAVATA-3468 configuration of URLs for retrieving external IDP userinfo
 discard a99970f  AIRAVATA-3468 Store IDP userinfo
 discard 96bc6bf  AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not
     add edb1f64  Vetur monorepo config
     add a43a997  AIRAVATA-3420 Remove usage of "move from filepath" user_storage functions
     add 0ae3cff  Making allowPreview not required since it has default
     add 96c5158  AIRAVATA-3420 'develop' branch to use 'mft-integration' branch of sdk
     add 6444833  AIRAVATA-3458 add gzip compression to file download
     add d5cf437  AIRAVATA-3458 Check filesize before loading file in editor
     add 010fa38  AIRAVATA-3458 import style fix
     add 5e5506e  AIRAVATA-3069 Order experiment inputs
     add bc60599  Adding readonly GRP view
     add c1e842d  Fixing resevations list
     add f42a5f4  Adding minor fixes
     add 26df97b  Merge pull request #55 from akbranam/AIRAVATA-3321-GRP-Readonly-View
     add 51d5189  Adding search by Job Id to experiments list
     add edd97d9  Merge pull request #59 from akbranam/AIRAVATA-3417-Search-JobId-And_Substring
     add 0870a6b  Merge branch 'staging'
     add 1d0a857  Merge branch 'develop' into staging
     add 2ff4d0c  AIRAVATA-3362 Handle missing experiment data dir
     add ee566e6  Merge branch 'staging' into develop
     add 956d591  AIRAVATA-3458 Check first if file exists before getting size
     add 0c30e76  AIRAVATA-3462 Fix verification that user can still access preferred grp
     add fe72a6d  Merge branch 'AIRAVATA-3462' into develop
     add d9b4e5b  AIRAVATA-3462 Turn map into list of accessible GRP ids
     add 8fe855d  Merge branch 'AIRAVATA-3462' into develop
     add 78ace00  Redirect with access token query parameter
     add 59a3f94  Handle redirect_uri that already has query params
     add dcf93fb  Merge branch 'jupyterhub-access-token-redirect' into develop
     add f35f3f3  Added async compute property and updated error messages
     add 171e6ea  Async validation implemented
     add 2cf5a09  Requirements for asyncComputed
     add 0f39b9a  Fixed styling in entry.js
     add 212c78b  Merge pull request #60 from docquantum/async-validation
     add b6dfed3  AIRAVATA-3460 Handle created directory with different name than requested
     add 19dd6aa  AIRAVATA-3460 allow passing path as query param
     add e86f35e  AIRAVATA-3458 Making UI more consistent
     add 2b61e15  fixing create new grp
     add 911a0f7  Merge pull request #61 from akbranam/AIRAVATA-3321-GRP-Readonly-View
     add f4d2599  AIRAVATA-3465 Upgrading wagtail to 2.9.3
     add 3536456  AIRAVATA-3465 Adding Wagtail Draftail Anchors plugin
     add 160ed99  AIRAVATA-3465 Upgrading Wagtail to 2.10.2
     add ba33fdb  AIRAVATA-3465 add id to headings for anchor links
     add e639195  AIRAVATA-3465 Upgrading Wagtail to 2.11 (latest LTS)
     add 3a8b585  AIRAVATA-3465 Upgrading DRF to be compatible with Wagtail 2.11
     add 3f08100  Merge branch 'airavata-3465' into develop
     add f9ba64e  Document and test support for Python 3.9
     add 304016b  Merge branch 'python39' into develop
     add fef84db  Merge branch 'AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo' into develop
     add 8eabba1  AIRAVATA-3455 Updating build scripts with auth frontend build
     add b7eb92b  AIRAVATA-3455 Fixing basename argument
     add 14c7f27  AIRAVATA-3455 Fixing linting error
     add cd3f6b4  Update Dockerfile to Python 3.9
     add 7932a08  Merge branch 'python39' into develop
     add 9ec9b00  Optimize docker image size: exclude node_modules
     add 8cce97c  AIRAVATA-3383 settings_local.py download
     add 27dc31b  Merge branch 'airavata-3383' into develop
     add f08c213  AIRAVATA-3833 Bug fixes
     add 3b022ea  Merge branch 'airavata-3383' into develop
     add 459e0a4  AIRAVATA-3383 Adding docs
     add 017a7a9  Merge branch 'airavata-3383' into develop
     add 702d905  AIRAVATA-3453 POC: initial supcrtbl2 custom interface with WCs
     add 1247098  AIRAVATA-3453 Initialize radio button inputs with value
     add 313da21  AIRAVATA-3453: Input event wrappers to simplify slot overrides
     add e54afd1  AIRAVATA-3453 Integrated jquery-textcomplete plugin
     add 27249cf  AIRAVATA-3453 Added checkOutput for validation
     add 815b524  AIRAVATA-3453 Initialize textarea value
     add 51d768c  AIRAVATA-3453 Update experiment input when autocomplete option selected
     add ff57469  AIRAVATA-3453 project-selector and including bootstrap vue styles
     add 1ade2d9  AIRAVATA-3453 group resource profile selector
     add 8e4ab93  AIRAVATA-3453 lint error
     add 0c18ce1  AIRAVATA-3453 Initial version of resource selection components
     add a1950f6  AIRAVATA-3453 Fixed async caching logic
     add cf98fdb  AIRAVATA-3453 Implemented queue settings editor
     add 93a52a4  AIRAVATA-3453 Apply GRP policy when it loads/changes
     add 6ed789a  AIRAVATA-3453 Handle user no longer has access to GRP
     add fa185af  AIRAVATA-3453 Add icons
     add 3d12286  AIRAVATA-3453 Calling context processor to populate species list
     add c26ec91  AIRAVATA-3453 Database model for registering custom application templates
     add a8aaa96  AIRAVATA-3453 Convenience base template for working with custom application templates
     add ce09e26  AIRAVATA-3453 Adding bootstrap styling to exp name
     add 1b54c88  AIRAVATA-3453 Integrate web components build into overall build
     add 47c4774  AIRAVATA-3163 Fix issue with header scrolling out of view when using scrollIntoView()
     add ab9f761  AIRAVATA-3163 Fix scrolling of sidebar
     add bd24746  AIRAVATA-3322 Paginated experiment statistics view
     add 0590d3b  AIRAVATA-3322 Fix Pager display for results that don't return counts
     add 9bddc7a  Revert "AIRAVATA-3322 Fix Pager display for results that don't return counts"
     add a9bb658  Revert "AIRAVATA-3322 Paginated experiment statistics view"
     add a0c2421  AIRAVATA-3322 performance improvements in the frontend for loading statistics
     add 11b160c  AIRAVATA-3420 Moved download to sdk
     add e15b154  AIRAVATA-3420 Fix headers for GET requests
     add 0afbc04  AIRAVATA-3420 Use get_lazy_download_url to get URL that redirects to actual download URL
     add c091a8c  AIRAVATA-3420 Change expected format of GATEWAY_DATA_STORE_REMOTE_API
     add 77c2943  AIRAVATA-3420 Adding missing import
     add 87bb191  AIRAVATA-3420 Newer SDK with MFT integration
     add b1e7f03  AIRAVATA-3420 Remove check for get_lazy_download_url
     add bac762c  AIRAVATA-3420 AIRAVATA-3469 Updating sdk version for get_lazy_download_url fix, folder download views
     add 19e3016  AIRAVATA-3469 UI for downloading directory
     add 31bb754  Merge branch 'mft-download' into develop
     add 2310586  AIRAVATA-3420 Adding g++ to docker image for grpc pip install
     add 3b9cefd  AIRAVATA-3475 Create symlinks to shared directories in user storage for each user
     add d52a245  Merge branch 'airavata-3475' into develop
     add 2137d83  AIRAVATA-3322 Pull the airavata-python-sdk from PyPI
     add c4e1683  AIRAVATA-3383 Long term we don't need SHARING_API settings, but for now they are needed for the portal to start up
     add 4004146  AIRAVATA-3420 Add back 'hidden' field for backwards compat in remote API
     add 99e552e  AIRAVATA-3487 Default write access to passed in GRP object
     add 9b9a1a9  AIRAVATA-3489 Check that editor was created before tearing it down
     add bad4d5d  AIRAVATA-3490 Fixes bug by making sure to return a queryset when user is not a gateway admin
     add fc3748b  AIRAVATA-3490 Fixes gateway admin check
     add e725c11  AIRAVATA-3319 Require login for user profile editor
     add 5e515b7  Handle refresh token failure
     add a481226  authz_token should already exist on request in login signal
     add fabcc8f  Fixes bug where missing ACCESS_TOKEN was interpreted as a good, unexpired token
     add b0a5658  AIRAVATA-3383 Add missing @login_required decorator
     add 885578c  AIRAVATA-3420 Return None for download url if data product doesn't exist in user storage
     add 3c3d1f7  favicon support
     add 4392395  AIRAVATA-3453 Adding Save and Launch button, with styling
     add 72486c6  AIRAVATA-3453 Adding post-save/launch navigation
     add 15cdc36  AIRAVATA-3453 switching to native slots
     add 861cd80  AIRAVATA-3453 Fix issue with selecting a GRP with no deployments
     add 176ce2b  Merge branch 'airavata-3453' into develop
     add 1df4f32  AIRAVATA-3469 Add corresponding download file link in storage browser
     add df6a84b  AIRAVATA-3484 Only reset the file upload widget once all uploads have finished
     add ecbf68c  AIRAVATA-3420 Fix backwards compatible use of GATEWAY_DATA_STORE_REMOTE_API in /launch/ api
     add 37fe5b9  AIRAVATA-3260 Fix prop that sets fixed table layout
     add a754a55  AIRAVATA-3428 Reload app interface when it changes
     add b727565  AIRAVATA-3485 Support for experiment-id in user-storage REST API
     add 33cf57d  AIRAVATA-3485 Update SDK to 1.1.dev4
     add 87985ca  AIRAVATA-3383 Remove Sharing API service settings from config
     add b68fdc7  Merge branch 'staging' into develop
     add 5c8005f  AIRAVATA-3383 Removing unnecessary SHARING API settings
     add 436e05a  Merge branch 'staging' into develop
     add 0f3d067  AIRAVATA-3488 Enable some additional feature in RichTextBlock
     add d42416c  Don't display image element until image data is loaded
     add be3c9e4  Merge branch 'staging' into develop
     add 97c6a02  AIRAVATA-3485 use experiment-id in GET requests also
     add 6a9168d  AIRAVATA-3485 Support experiment data directory relative path for getting file metadata
     add cff35bb  AIRAVATA-3460 Don't require path for file metadata response
     add 91cf7e7  AIRAVATA-3322 Paginated experiment statistics view
     add 6ee15c8  AIRAVATA-3322 Fix Pager display for results that don't return counts
     add 0342d80  AIRAVATA-3322 Update to version of python SDK with paginated getExperimentStatistics
     add 0dae326  AIRAVATA-3485 Update SDK to 1.1.dev6
     add 6e8c6dd  AIRAVATA-3485 Update SDK to 1.1.dev7
     add 353b470  AIRAVATA-3485 Update SDK to 1.1.dev8
     add 9bb685a  AIRAVATA-3492 Handle deselecting the default queue in ApplicationDeploymentEditor
     add c722afa  Updated output view provider portion of tutorial to use cookiecutter
     add 61de74e  Updated custom django app portion of tutorial to use cookiecutter
     add 5219107  updating notes on viewing final solution
     add 5f0ee98  tutorial instructions on installing Python and creating venv on all OSes
     add 8a22eec  tutorial: minor fixes
     add f63d5c8  tutorial: rename to "custom ui tutorial"
     add 3b11429  tutorial: updated screenshots
     add 31b34c3  Compatibility for building JS on Windows
     add 0236c1f  tutorial: Python on Windows instructions
     add 6921aaf  Merge pull request #62 from apache/python39
     add 722fa98  Merge pull request #63 from apache/airavata-3465
     add 564a8b1  Merge branch 'master' of https://github.com/apache/airavata-django-portal
     add 2169ef7  tutorial: tabbed instructions for different OS/environments
     add 31a8913  tutorial: update tab style to match admonitions
     add 2d2b016  tutorial: adding local python instructions for mac/linux
     add 33bbcfb  tutorial: cleaned up windows home guidance
     add 6f9048f  tutorial: windows instructions equivalent to `cd $HOME`
     add 942d997  tutorial: tweak to windows instructions
     add c024dbc  tutorial: updating host for input customization section
     add de23f6e  tutorial: rename solution repo to prevent collisions
     add 1c6b7d7  tutorial: fixing broken links
     add 316d06c  tutorial: adding a step to wait until docker container starts up
     add fe61b8c  tutorial: updating name of GRP, compute resource
     add 461b9a5  tutorial: adding some clarification about which inputs are configured
     add fe4ef5f  tutorial: updating order of output view provider steps
     add 6707998  AIRAVATA-3420 Switch to published versions of SDK
     add b2317c9  Adds instructions on building multi-arch Docker images
     add 3e15aec  tutorial: expanded guidance and verification steps for installing prerequisites
     add 305b02c  Updated docs with instructions on how to use cookiecutter project templates
     add e5f76f6  tutorial: updating instructions for Windows PowerShell
     add e6f8735  tutorial: clarify why we generate custom Django app code as prerequisite to custom output view provider
     add ec660ed  Merge branch 'master' into develop
     add 0dabd4b  AIRAVATA-3491 Utility method for constructing simple Experiment object
     add fcf01b9  AIRAVATA-3491 Unit tests for ExperimentUtils.js
     add 9eed16f  AIRAVATA-3491 Accept application module id as input too
     add 52244a9  AIRAVATA-3491 Add applicationInterfaceId option to createExperiment
     add 307e518  AIRAVATA-3491: tutorial: update to use createExperiment utility
     add ed34c69  AIRAVATA-3420 Update SDK to 1.2.dev1
     add d81e04b  AIRAVATA-3420 Updates for the refactored USER_STORAGES setting
     add d777e4f  AIRAVATA-3491 Fix common UI build with async syntax
     add 8f2c51d  Update caniuse-db
     add 6d2bd4e  Log ObjectDoesNotExist errors at warning level
     add 13fe2f2  Update blocks.py
     add 1086c90  Merge pull request #64 from musecian/patch-1
     add ed90e2f  Create buttonmore.html
     add 4b28189  Merge pull request #65 from musecian/patch-2
     add 76cb794  Update settings.py
     add 0906deb  Merge pull request #66 from musecian/patch-3
     add 2e9c963  AIRAVATA-3499 RawHTMLBlock integration
     add e3ca58f  AIRAVATA-3500 Update label and icon of CodeBlock
     add 64e96ff  AIRAVATA-3488 Removing unused css
     add 87eaf40  AIRAVATA-3488 Adding classes to style rendered blockquote
     add fc24908  AIRAVATA-3488 Update Wagtail to 2.11.8 (security fix)
     add c5532b3  AIRAVATA-3501 more button name changes for clarity
     add af12e13  AIRAVATA-3488 linting fixes
     add f1d978b  AIRAVATA-3420 Update SDK to 1.2.0
     add 1b06513  AIRAVATA-3420 Fix call to get_default_storage_resource_id
     add 91d31dd  AIRAVATA-3420 Update SDK to 1.2.1
     add fb1c4ee  AIRAVATA-3502 Handle edge case that refresh token fails because user is logged out in another client
     add 9f993b3  Merge branch 'develop' into staging
     add f2af279  AIRAVATA-3507 Only show download experiment dir link if it exists
     add c0d725a  Updating Django dependency to latest security fix
     add f5160a2  AIRAVATA-3510 When getApplicationInterface fails, check to see if it failed because it no longer exists, and return a 404
     add 1924b7b  AIRAVATA-3497 JS utility code for loading an input or output file
     add e415a97  AIRAVATA-3497 Utility for downloading a data product
     add e1bf066  AIRAVATA-3497 tutorial: updating how to download output file with ExperimentUtils
     add 9763265  AIRAVATA-3497 Updated tutorial to use async/await syntax
     add 789a602  Merge branch 'airavata-3497' into develop
     add abcceab  Merge branch 'staging'
     add 42c8bea  Merge branch 'staging'
     add 90d08ed  Merge branch 'airavata-3497'
     add 84db898  AIRAVATA-3383 Fix creating the GATEWAY_DATA_STORE_REMOTE_API setting
     add f421c01  Merge branch 'master' into develop
     add ce2da1d  AIRAVATA-3509 When parsed slider value changes, update the value
     add e76559c  Merge branch 'airavata-3509' into develop
     add 68b3425  AIRAVATA-3511 Change "a" to "an" when password account name starts vowel
     add e37a60c  Merge branch 'master' into develop
     add 2c03c27  Side by side group view with bootstrap-vue update
     add f4a8f4d  Adding GroupMembersEditor.vue
     add 3ac6666  Adding GroupMembersDetailsContainer.vue and updating yarn.lock and package.json files
     add 8749fd0  adding fixes
     add c8af530  fixing package.json
     add 013a2c4  Fixing 'add/remove all members' bug
     add 257a1b0  Merge branch 'AIRAVATA-2809-GROUP-VIEW' of https://github.com/akbranam/airavata-django-portal into akbranam-AIRAVATA-2809-GROUP-VIEW
     add 1dee350  Merge branch 'akbranam-AIRAVATA-2809-GROUP-VIEW' into merge-pr67
     add 7e79e1e  AIRAVATA-2809 Cleaning up lint errors, generating lock files
     add 13a8dca  tutorial: copy to clipboard button
     add 503e96b  Update sdk to 1.2.2: fixes returning 404 when file not found
     add dd523d2  tutorial: adding copy to clipboard buttons
     add d0628f8  AIRAVATA-3513 Remove vendored import
     add 41323ab  AIRAVATA-3513 set DEFAULT_AUTO_FIELD
     add ab11ba0  AIRAVATA-3513 Fixing warnings
     add 507572c  AIRAVATA-3513 Upgrade Django, DRF and Wagtail
     add 01268ae  Merge branch 'django32-upgrade' into develop
     add 6f2f237  AIRAVATA-3513 Fixed tests: added new hash field to embed fixtures
     add 4eb3445  AIRAVATA-3515 Fix overflow/wrapping of large statistics numbers
     add d15277e  AIRAVATA-3516 Disable download directory links when too large
     add a5e61bf  AIRAVATA-3517 Include IsAuthenticated is required permissions for IAMUserViewSet and UnverifiedEmailUserViewSet
     add 1b923ad  AIRAVATA-3477 StringInputEditor web component
     add 887397c  AIRAVATA-3477 Convert to Vuex for complex shared state between web components
     add 3841787  AIRAVATA-3477 Finish converting to Vuex
     add 8081d38  AIRAVATA-3477 bottom margin on QueueSettingsEditor
     add fa82a91  AIRAVATA-3477 Programmatically define slots and make compute and queue editors standalone
     add 9d3931f  AIRAVATA-3477 Fix issue with native input event colliding with web component input event
     add 99eaba7  AIRAVATA-3477 Fixes to setting initial project and GRP id
     add bdf764c  AIRAVATA-3477 Cleaning up after Vuex refactor
     add 154bce8  AIRAVATA-3477 radio button input editor web component
     add 639ad16  AIRAVATA-3477 Unit tests for web comp vuex store
     add c9e6e26  AIRAVATA-3477 Mixin for input editor web components
     add 19f0ba2  AIRAVATA-3477 Wrapped FileInputEditor as web component
     add 06c38f1  AIRAVATA-3477 Wrapped CheckboxInputEditor as web component
     add 60989a9  AIRAVATA-3477 Wrapped MultiFileInputEditor as web component
     add 9b4b123  AIRAVATA-3477 Wrapped RangeSliderInputEditor as web component
     add 49dfb23  AIRAVATA-3477 Wrapped SelectInputEditor as web component
     add b7f2134  AIRAVATA-3477 Wrapped SliderInputEditor as web component
     add 139a596  AIRAVATA-3477 Wrapped TextareaInputEditor as web component
     add ea6d5b9  AIRAVATA-3477 test logic around initializing experiment's GRP
     add 989bbc3  AIRAVATA-3477 more tests for initializing compute resource and queue settings
     add 13df7fa  AIRAVATA-3477 Support inline options markup
     add e881310  AIRAVATA-3477 Support setting queue name via web component attribute
     add 9aeecfc  AIRAVATA-3477 add backwards compat for updateInputValue
     add 69da810  Merge branch 'airavata-3477' into develop
     new 46577da  AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not
     new 7b0e99b  AIRAVATA-3468 Store IDP userinfo
     new 8900d32  AIRAVATA-3468 configuration of URLs for retrieving external IDP userinfo
     new 5adf6a7  AIRAVATA-3319 Admin API for updating a user's username
     new 4ce2dd2  AIRAVATA-3319 Add admin UI for updating user's username
     new 7cf8d25  AIRAVATA-3468 Disabled username editing by users
     new 530e1cb  AIRAVATA-3468 Allow /media/ in completeness check middleware (gateway logo)
     new 4d910c9  AIRAVATA-3468 Inform user that they must complete profile
     new 95582e7  AIRAVATA-3319 Add admin email alerting when user ends up with invalid username
     new 8648a4c  AIRAVATA-3468 Add link for navigating back to the dashboard
     new 5801261  AIRAVATA-3468 Only redirect web page (Accepts: text/html) requests to complete profile
     new 5d765ac  AIRAVATA-3468 Create user profile if it doesn't exist
     new 723cf77  WIP

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (26a192d)
            \
             N -- N -- N   refs/heads/AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo (723cf77)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .dockerignore                                      |    1 +
 .travis.yml                                        |    2 +
 Dockerfile                                         |   22 +-
 README.md                                          |   42 +-
 build_js.bat                                       |   36 +
 build_js.sh                                        |    1 +
 django_airavata/app_config.py                      |    3 +-
 django_airavata/apps/admin/apps.py                 |    8 +
 django_airavata/apps/admin/package.json            |    6 +-
 .../BatchQueueResourcePolicy.vue                   |    7 +
 .../ComputePreference.vue                          |   25 +-
 .../ComputeResourcePolicyEditor.vue                |    6 +
 .../ComputeResourceReservationList.vue             |    8 +-
 .../GroupComputeResourcePreference.vue             |   50 +-
 .../applications/ApplicationDeploymentEditor.vue   |   18 +-
 .../applications/ApplicationDeploymentsList.vue    |    2 +-
 .../applications/ApplicationInputFieldEditor.vue   |    2 +-
 .../applications/ApplicationOutputFieldEditor.vue  |    2 +-
 .../ComputeResourcePreferenceDashboard.vue         |   18 +-
 .../dashboards/CredentialStoreDashboard.vue        |   10 +-
 .../components/developers/DevelopersContainer.vue  |   18 +
 .../gatewayprofile/StoragePreferenceList.vue       |    6 +-
 .../notices/NoticesManagementContainer.vue         |    6 +-
 .../statistics/ExperimentStatisticsCard.vue        |   22 +-
 .../statistics/ExperimentStatisticsContainer.vue   |   68 +-
 .../src/components/users/ChangeUsernamePanel.vue   |   92 +
 .../IdentityServiceUserManagementContainer.vue     |   17 +-
 .../UnverifiedEmailUserManagementContainer.vue     |    4 +-
 .../src/components/users/UserDetailsContainer.vue  |    7 +
 .../static/django_airavata_admin/src/router.js     |    8 +-
 django_airavata/apps/admin/urls.py                 |   25 +-
 django_airavata/apps/admin/views.py                |    6 +
 django_airavata/apps/admin/yarn.lock               |  123 +-
 django_airavata/apps/api/admin.py                  |   24 +
 django_airavata/apps/api/exceptions.py             |    2 +-
 django_airavata/apps/api/helpers.py                |   23 +-
 ...template_applicationtemplatecontextprocessor.py |   39 +
 django_airavata/apps/api/models.py                 |   26 +
 django_airavata/apps/api/package.json              |    7 +-
 django_airavata/apps/api/serializers.py            |   44 +-
 django_airavata/apps/api/signals.py                |    8 +-
 .../django_airavata_api/js/errors/ErrorUtils.js    |    3 +
 .../api/static/django_airavata_api/js/index.js     |   12 +-
 .../js/models/ApplicationInterfaceDefinition.js    |   11 +
 .../django_airavata_api/js/models/DataProduct.js   |    1 +
 .../django_airavata_api/js/models/Experiment.js    |    8 +
 .../js/models/ExperimentSearchFields.js            |    1 +
 .../js/models/GroupComputeResourcePreference.js    |    1 +
 .../static/django_airavata_api/js/models/User.js   |   11 +-
 .../js/models/validators/ValidatorFactory.js       |    2 +-
 .../django_airavata_api/js/service_config.js       |   11 +
 .../js/utils/ExperimentUtils.js                    |  254 ++
 .../django_airavata_api/js/utils/FetchUtils.js     |    3 +-
 .../js/utils/PaginationIterator.js                 |   11 +-
 .../tests/utils/ExperimentUtils.test.js            |  589 +++
 django_airavata/apps/api/thrift_utils.py           |    4 +-
 django_airavata/apps/api/tus.py                    |   12 +-
 django_airavata/apps/api/urls.py                   |  109 +-
 django_airavata/apps/api/views.py                  |  153 +-
 django_airavata/apps/api/yarn.lock                 |   39 +-
 django_airavata/apps/auth/backends.py              |   34 +-
 django_airavata/apps/auth/middleware.py            |    9 +-
 django_airavata/apps/auth/models.py                |    3 +-
 django_airavata/apps/auth/package.json             |    4 +-
 django_airavata/apps/auth/serializers.py           |   17 +-
 django_airavata/apps/auth/signals.py               |    7 +-
 .../js/components/UserProfileEditor.vue            |   38 +-
 .../js/containers/UserProfileContainer.vue         |   14 +
 .../django_airavata_auth/create_account.html       |    2 +-
 .../settings_local.py.template}                    |   67 +-
 django_airavata/apps/auth/urls.py                  |   55 +-
 django_airavata/apps/auth/utils.py                 |   84 +-
 django_airavata/apps/auth/views.py                 |  121 +-
 django_airavata/apps/auth/yarn.lock                |  338 +-
 django_airavata/apps/dataparsers/package.json      |    3 +-
 django_airavata/apps/dataparsers/urls.py           |   12 +-
 django_airavata/apps/dataparsers/yarn.lock         |   44 +-
 django_airavata/apps/groups/package.json           |    4 +-
 .../js/group_components/GroupEditor.vue            |    9 +-
 .../GroupMembersDetailsContainer.vue               |   72 +
 .../js/group_components/GroupMembersEditor.vue     |  472 ++-
 django_airavata/apps/groups/urls.py                |    8 +-
 django_airavata/apps/groups/yarn.lock              | 4426 ++++++++++----------
 .../js/input-editors/InputEditorMixin.js           |   34 +-
 .../package.json                                   |    7 +-
 .../django-airavata-workspace-plugin-api/yarn.lock |   39 +-
 django_airavata/apps/workspace/package.json        |   21 +-
 .../static/django_airavata_workspace/.gitignore    |    1 +
 .../input-editors/CheckboxInputEditor.vue          |   27 +-
 .../experiment/input-editors/FileInputEditor.vue   |    7 +
 .../input-editors/RadioButtonInputEditor.vue       |   26 +-
 .../input-editors/RangeSliderInputEditor.vue       |   86 +-
 .../experiment/input-editors/SelectInputEditor.vue |   22 +-
 .../experiment/input-editors/SliderInputEditor.vue |   76 +-
 .../experiment/input-editors/StringInputEditor.vue |    3 +
 .../input-editors/TextareaInputEditor.vue          |   14 +-
 .../output-displays/ImageOutputDisplay.vue         |    4 +-
 .../storage/ExperimentStoragePathViewer.vue        |   29 +-
 .../storage/ExperimentStorageViewContainer.vue     |   52 +-
 .../components/storage/UserStorageCreateView.vue   |    6 +-
 .../storage/UserStorageFileSelectionContainer.vue  |    1 +
 .../components/storage/UserStoragePathViewer.vue   |   43 +-
 .../storage-edit/UserStorageDownloadButton.vue     |    2 +-
 .../storage/storage-edit/UserStorageEditViewer.vue |    2 +-
 .../storage/storage-edit/UserStorageLink.vue       |   30 +-
 .../storage-edit/UserStorageTextEditViewer.vue     |   85 +-
 .../js/containers/EditExperimentContainer.vue      |   13 +-
 .../js/containers/ExperimentListContainer.vue      |  159 +-
 .../js/containers/RecentExperimentsContainer.vue   |   13 +-
 .../js/containers/UserStorageContainer.vue         |   59 +-
 .../js/web-components/ComputeResourceSelector.vue  |   95 +
 .../ExperimentComputeResourceSelector.vue          |   43 +
 .../js/web-components/ExperimentEditor.vue         |  309 ++
 .../GroupResourceProfileSelector.vue               |   95 +
 .../js/web-components/ProjectSelector.vue          |   95 +
 .../js/web-components/QueueSettingsEditor.vue      |  228 +
 .../input-editors/CheckboxInputEditor.vue          |   50 +
 .../input-editors/FileInputEditor.vue              |   43 +
 .../input-editors/InlineOptionsMixin.js            |   50 +
 .../input-editors/MultiFileInputEditor.vue         |   43 +
 .../input-editors/RadioButtonInputEditor.vue       |   50 +
 .../input-editors/RangeSliderInputEditor.vue       |   66 +
 .../input-editors/SelectInputEditor.vue            |   48 +
 .../input-editors/SliderInputEditor.vue            |   64 +
 .../input-editors/StringInputEditor.vue            |   41 +
 .../input-editors/TextareaInputEditor.vue          |   45 +
 .../input-editors/WebComponentInputEditorMixin.js  |   62 +
 .../js/web-components/store.js                     |  654 +++
 .../js/web-components/styles.scss                  |    5 +
 .../tests/unit/web-components/store.spec.js        |  708 ++++
 .../django_airavata_workspace/wc-base.html         |   27 +
 django_airavata/apps/workspace/urls.py             |   26 +-
 django_airavata/apps/workspace/views.py            |   48 +-
 django_airavata/apps/workspace/vue.config.js       |   15 +-
 django_airavata/apps/workspace/yarn.lock           |  205 +-
 django_airavata/middleware.py                      |   17 -
 django_airavata/settings.py                        |  211 +-
 django_airavata/settings_local.py.ide              |   18 +-
 django_airavata/settings_local.py.sample           |   35 +-
 django_airavata/static/common/babel.config.js      |    2 +-
 .../common/js/components/ApplicationName.vue       |   31 +-
 .../{DeleteButton.vue => ConfirmationButton.vue}   |   26 +-
 .../static/common/js/components/Pager.vue          |    9 +-
 django_airavata/static/common/js/entry.js          |    2 +
 django_airavata/static/common/js/index.js          |    2 +
 django_airavata/static/common/package.json         |   16 +-
 django_airavata/static/common/scss/main.scss       |   15 +-
 django_airavata/static/common/yarn.lock            |  505 ++-
 django_airavata/static/css/draft-colors.css        |    3 -
 django_airavata/templates/base.html                |    1 +
 .../templates/blocks/bootstrap/buttonmore.html     |    7 +
 .../templates/blocks/heading_block.html            |    8 +-
 .../includes/favicon.html                          |    9 +
 django_airavata/templates/includes/head.html       |    3 +-
 django_airavata/urls.py                            |   38 +-
 django_airavata/utils.py                           |   27 -
 django_airavata/wagtailapps/base/blocks.py         |   46 +
 .../wagtailapps/base/fixtures/tests/default.json   |    9 +-
 .../base/migrations/0026_auto_20210923_2212.py     |   48 +
 .../base/templatetags/navigation_tags.py           |   12 +-
 django_airavata/wagtailapps/base/wagtail_hooks.py  |   56 +-
 docs/assets/css/pymdownx.tabbed.css                |   47 +
 docs/assets/js/clipboard.min.js                    |    7 +
 docs/assets/js/clipboard_init.js                   |    1 +
 docs/dev/custom_django_app.md                      |  295 +-
 docs/dev/custom_output_view_provider.md            |  123 +-
 docs/dev/new_django_app.md                         |    6 +-
 docs/tutorial/custom_ui_tutorial.md                | 1753 ++++++++
 docs/tutorial/gateways_tutorial.md                 | 1401 -------
 .../screenshots/custom-ui/custom-app-menu.png      |  Bin 0 -> 177809 bytes
 .../gaussian-output-view-providers-json.png        |  Bin 0 -> 31419 bytes
 .../screenshots/custom-ui/settings_menu.png        |  Bin 0 -> 181704 bytes
 lint_js.sh                                         |    1 +
 mkdocs.yml                                         |   10 +-
 requirements-dev.txt                               |    3 +-
 requirements.txt                                   |   20 +-
 setup.py                                           |    1 -
 tests/settings.py                                  |    4 -
 tox.ini                                            |    4 +-
 vetur.config.js                                    |   11 +
 180 files changed, 11571 insertions(+), 5170 deletions(-)
 create mode 100644 build_js.bat
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/developers/DevelopersContainer.vue
 create mode 100644 django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue
 create mode 100644 django_airavata/apps/api/migrations/0006_applicationtemplate_applicationtemplatecontextprocessor.py
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/utils/ExperimentUtils.js
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/tests/utils/ExperimentUtils.test.js
 copy django_airavata/{settings_local.py.sample => apps/auth/templates/django_airavata_auth/settings_local.py.template} (73%)
 create mode 100644 django_airavata/apps/groups/static/django_airavata_groups/js/group_components/GroupMembersDetailsContainer.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/.gitignore
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ComputeResourceSelector.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentComputeResourceSelector.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ExperimentEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/GroupResourceProfileSelector.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ProjectSelector.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/CheckboxInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/FileInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/InlineOptionsMixin.js
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/MultiFileInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RadioButtonInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/RangeSliderInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SelectInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/SliderInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/StringInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/TextareaInputEditor.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/input-editors/WebComponentInputEditorMixin.js
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/styles.scss
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/tests/unit/web-components/store.spec.js
 create mode 100644 django_airavata/apps/workspace/templates/django_airavata_workspace/wc-base.html
 copy django_airavata/static/common/js/components/{DeleteButton.vue => ConfirmationButton.vue} (52%)
 delete mode 100644 django_airavata/static/css/draft-colors.css
 create mode 100644 django_airavata/templates/blocks/bootstrap/buttonmore.html
 create mode 100644 django_airavata/templates/django_airavata_wagtail_base/includes/favicon.html
 create mode 100644 django_airavata/wagtailapps/base/migrations/0026_auto_20210923_2212.py
 create mode 100644 docs/assets/css/pymdownx.tabbed.css
 create mode 100644 docs/assets/js/clipboard.min.js
 create mode 100644 docs/assets/js/clipboard_init.js
 create mode 100644 docs/tutorial/custom_ui_tutorial.md
 delete mode 100644 docs/tutorial/gateways_tutorial.md
 create mode 100644 docs/tutorial/screenshots/custom-ui/custom-app-menu.png
 create mode 100644 docs/tutorial/screenshots/custom-ui/gaussian-output-view-providers-json.png
 create mode 100644 docs/tutorial/screenshots/custom-ui/settings_menu.png
 create mode 100644 vetur.config.js

[airavata-django-portal] 09/13: AIRAVATA-3319 Add admin email alerting when user ends up with invalid username

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 95582e7cb7969217d5b0a727e2d955ce1ea61e89
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jul 27 15:19:03 2021 -0400

    AIRAVATA-3319 Add admin email alerting when user ends up with invalid username
---
 django_airavata/apps/auth/backends.py | 10 ++++++++
 django_airavata/apps/auth/utils.py    | 45 +++++++++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+)

diff --git a/django_airavata/apps/auth/backends.py b/django_airavata/apps/auth/backends.py
index 2197928..a60fd59 100644
--- a/django_airavata/apps/auth/backends.py
+++ b/django_airavata/apps/auth/backends.py
@@ -239,6 +239,16 @@ class KeycloakBackend(object):
         user.first_name = first_name
         user.last_name = last_name
         user.save()
+
+        # Since only Admins can fix a bad username, alert Admins if the user has
+        # an invalid username
+        if not user_profile.is_username_valid:
+            try:
+                utils.send_admin_alert_about_invalid_username(
+                    request, username, email, first_name, last_name)
+            except Exception:
+                logger.exception(f"Failed to send alert about username being invalid: {username}")
+
         return user
 
     def _get_or_create_user(self, sub, username):
diff --git a/django_airavata/apps/auth/utils.py b/django_airavata/apps/auth/utils.py
index bb9e82d..b9419a6 100644
--- a/django_airavata/apps/auth/utils.py
+++ b/django_airavata/apps/auth/utils.py
@@ -141,6 +141,51 @@ def send_new_user_email(request, username, email, first_name, last_name):
     msg.send()
 
 
+def send_admin_alert_about_invalid_username(request, username, email, first_name, last_name):
+    domain, port = split_domain_port(request.get_host())
+    context = Context({
+        "username": username,
+        "email": email,
+        "first_name": first_name,
+        "last_name": last_name,
+        "portal_title": settings.PORTAL_TITLE,
+        "gateway_id": settings.GATEWAY_ID,
+        "http_host": domain,
+    })
+    subject = Template("Please fix invalid username: a user of {{portal_title}} ({{http_host}}) has an invalid username ({{username}})").render(context)
+    body = Template("""
+    <p>
+    Dear Admin,
+    </p>
+
+    <p>
+    The following user has an invalid username:
+    </p>
+
+    <p>Username: {{username}}</p>
+    <p>Name: {{first_name}} {{last_name}}</p>
+    <p>Email: {{email}}</p>
+
+    <p>
+    This likely happened because there was no appropriate user attribute to use
+    for the user's username when the user logged in through an external identity
+    provider.  Please update the username to the user's email address or some
+    other valid value in the <a href="https://{{http_host}}/admin/users/">Manage
+    Users</a> view in the portal.
+    </p>
+    """.strip()).render(context)
+    msg = EmailMessage(subject=subject,
+                       body=body,
+                       from_email="{} <{}>".format(
+                           settings.PORTAL_TITLE,
+                           settings.SERVER_EMAIL),
+                       to=[a[1] for a in getattr(settings,
+                                                 'PORTAL_ADMINS',
+                                                 settings.ADMINS)])
+    msg.content_subtype = 'html'
+    msg.send()
+
+
 def send_email_to_user(template_id, context):
     email_template = models.EmailTemplate.objects.get(pk=template_id)
     subject = Template(email_template.subject).render(context)

[airavata-django-portal] 06/13: AIRAVATA-3468 Disabled username editing by users

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 7cf8d2504baec72859905cd1d6f7128d7db163ff
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jul 7 13:50:28 2021 -0400

    AIRAVATA-3468 Disabled username editing by users
---
 .../django_airavata_auth/js/components/UserProfileEditor.vue      | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index b6b31fe..23b9104 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -3,12 +3,13 @@
     <b-form-group label="Username">
       <b-form-input
         v-model="$v.user.username.$model"
-        @keydown.native.enter="save"
+        :disabled="true"
         :state="validateState($v.user.username)"
       />
       <b-form-invalid-feedback v-if="!$v.user.username.emailOrMatchesRegex">
         Username can only contain lowercase letters, numbers, underscores and
-        hyphens OR it can be the same as the email address.
+        hyphens OR it can be the same as the email address. Only an
+        administrator can update your username to a valid value.
       </b-form-invalid-feedback>
     </b-form-group>
     <b-form-group label="First Name">
@@ -65,6 +66,9 @@ export default {
       required: true,
     },
   },
+  created() {
+    this.$v.user.$touch();
+  },
   data() {
     return {
       user: this.cloneValue(),

[airavata-django-portal] 05/13: AIRAVATA-3319 Add admin UI for updating user's username

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 4ce2dd26cbe093b611f4749add918d04e05984f2
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jul 6 12:55:54 2021 -0400

    AIRAVATA-3319 Add admin UI for updating user's username
---
 django_airavata/apps/admin/package.json            |  1 +
 .../src/components/users/ChangeUsernamePanel.vue   | 92 ++++++++++++++++++++++
 .../IdentityServiceUserManagementContainer.vue     |  9 +++
 .../src/components/users/UserDetailsContainer.vue  |  7 ++
 django_airavata/apps/admin/yarn.lock               |  5 ++
 .../django_airavata_api/js/service_config.js       |  8 ++
 .../common/js/components/ConfirmationButton.vue    | 52 ++++++++++++
 django_airavata/static/common/js/index.js          |  2 +
 8 files changed, 176 insertions(+)

diff --git a/django_airavata/apps/admin/package.json b/django_airavata/apps/admin/package.json
index 670f5f5..94fde2f 100644
--- a/django_airavata/apps/admin/package.json
+++ b/django_airavata/apps/admin/package.json
@@ -26,6 +26,7 @@
     "vue-resource": "^1.3.4",
     "vue-router": "^2.7.0",
     "vuedraggable": "^2.16.0",
+    "vuelidate": "^0.7.6",
     "vuex": "^2.4.0",
     "weekstart": "^1.0.0"
   },
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue
new file mode 100644
index 0000000..42bc64b
--- /dev/null
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/ChangeUsernamePanel.vue
@@ -0,0 +1,92 @@
+<template>
+  <b-card header="Change Username">
+    <p class="card-text">
+      This will change the user's username in the identity service. NOTE: if
+      this user already has an Airavata User Profile, giving the user a new
+      username will result in the user getting a new Airavata User Profile and
+      losing the old one. Also, after updating the username the user will need
+      to log out and log back in.
+    </p>
+    <b-form-group label="New Username" label-for="new-username">
+      <b-input-group>
+        <b-form-input
+          id="new-username"
+          v-model="$v.newUsername.$model"
+          :state="validateState($v.newUsername)"
+        />
+        <b-input-group-append>
+          <b-button @click="newUsername = email">Copy Email Address</b-button>
+        </b-input-group-append>
+      </b-input-group>
+      <b-form-invalid-feedback
+        :state="validateState($v.newUsername)"
+        v-if="!$v.newUsername.emailOrMatchesRegex"
+      >
+        Username can only contain lowercase letters, numbers, underscores and
+        hyphens OR it can be the same as the email address.
+      </b-form-invalid-feedback>
+    </b-form-group>
+    <confirmation-button
+      variant="primary"
+      @confirmed="updateUsername"
+      :disabled="$v.$invalid"
+      dialog-title="Please confirm username change"
+    >
+      Please confirm that you want to change the user's username to
+      <strong>{{ newUsername }}</strong
+      >. NOTE: if this user already has an Airavata User Profile, giving the
+      user a new username will result in
+      <strong
+        >the user getting a new Airavata User Profile and losing the old
+        one</strong
+      >. Also, after updating the username the user will need to log out and log
+      back in.
+    </confirmation-button>
+  </b-card>
+</template>
+
+<script>
+import { components, errors } from "django-airavata-common-ui";
+import { validationMixin } from "vuelidate";
+import { helpers, or, required, sameAs } from "vuelidate/lib/validators";
+export default {
+  name: "change-username-panel",
+  mixins: [validationMixin],
+  props: {
+    username: {
+      type: String,
+      required: true,
+    },
+    email: {
+      type: String,
+      required: true,
+    },
+  },
+  components: {
+    "confirmation-button": components.ConfirmationButton,
+  },
+  data() {
+    return {
+      newUsername: this.username,
+    };
+  },
+  validations() {
+    const usernameRegex = helpers.regex("newUsername", /^[a-z0-9_-]+$/);
+    const emailOrMatchesRegex = or(usernameRegex, sameAs("email"));
+    return {
+      newUsername: {
+        required,
+        emailOrMatchesRegex,
+      },
+    };
+  },
+  methods: {
+    updateUsername() {
+      if (!this.$v.$invalid) {
+        this.$emit("update-username", [this.username, this.newUsername]);
+      }
+    },
+    validateState: errors.vuelidateHelpers.validateState,
+  },
+};
+</script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
index f7785da..458d718 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/IdentityServiceUserManagementContainer.vue
@@ -47,6 +47,7 @@
                   @groups-updated="groupsUpdated"
                   @enable-user="enableUser"
                   @delete-user="deleteUser"
+                  @update-username="updateUsername(data.item, ...$event)"
                 />
               </template>
             </b-table>
@@ -204,6 +205,14 @@ export default {
         this.reloadUserProfiles()
       );
     },
+    updateUsername(userProfile, username, newUsername) {
+      const updatedUserProfile = userProfile.clone();
+      updatedUserProfile.userId = newUsername;
+      services.IAMUserProfileService.updateUsername({
+        lookup: username,
+        data: updatedUserProfile,
+      }).finally(() => this.reloadUserProfiles());
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
index 48b9516..e59428f 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/users/UserDetailsContainer.vue
@@ -34,6 +34,11 @@
       :username="iamUserProfile.userId"
       @delete-user="$emit('delete-user', $event)"
     />
+    <change-username-panel
+      :username="iamUserProfile.userId"
+      :email="iamUserProfile.email"
+      @update-username="$emit('update-username', $event)"
+    />
   </div>
 </template>
 <script>
@@ -42,6 +47,7 @@ import UserGroupMembershipEditor from "./UserGroupMembershipEditor";
 import ActivateUserPanel from "./ActivateUserPanel";
 import EnableUserPanel from "./EnableUserPanel";
 import DeleteUserPanel from "./DeleteUserPanel";
+import ChangeUsernamePanel from "./ChangeUsernamePanel.vue";
 
 export default {
   name: "user-details-container",
@@ -60,6 +66,7 @@ export default {
     EnableUserPanel,
     DeleteUserPanel,
     ActivateUserPanel,
+    ChangeUsernamePanel,
   },
   data() {
     return {
diff --git a/django_airavata/apps/admin/yarn.lock b/django_airavata/apps/admin/yarn.lock
index 1e34aef..8b1d3b2 100644
--- a/django_airavata/apps/admin/yarn.lock
+++ b/django_airavata/apps/admin/yarn.lock
@@ -8574,6 +8574,11 @@ vuedraggable@^2.16.0:
   dependencies:
     sortablejs "^1.10.1"
 
+vuelidate@^0.7.6:
+  version "0.7.6"
+  resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.6.tgz#84100c13b943470660d0416642845cd2a1edf4b2"
+  integrity sha512-suzIuet1jGcyZ4oUSW8J27R2tNrJ9cIfklAh63EbAkFjE380iv97BAiIeolRYoB9bF9usBXCu4BxftWN1Dkn3g==
+
 vuex@^2.4.0:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/vuex/-/vuex-2.5.0.tgz#20f0265ade6c9a5ac6724a405d3ffdb4726c9741"
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 64ad6b1..d3c82b0 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -287,6 +287,14 @@ export default {
         requestType: "post",
         modelClass: IAMUserProfile,
       },
+      updateUsername: {
+        url: "/api/iam-user-profiles/<lookup>/update_username/",
+        bodyParams: {
+          name: "data",
+        },
+        requestType: "put",
+        modelClass: IAMUserProfile,
+      },
     },
     queryParams: ["limit", "offset", "search"],
     modelClass: IAMUserProfile,
diff --git a/django_airavata/static/common/js/components/ConfirmationButton.vue b/django_airavata/static/common/js/components/ConfirmationButton.vue
new file mode 100644
index 0000000..c327045
--- /dev/null
+++ b/django_airavata/static/common/js/components/ConfirmationButton.vue
@@ -0,0 +1,52 @@
+<template>
+  <div class="confirmation-button">
+    <b-button
+      :variant="variant"
+      @click="$refs.modal.show()"
+      :disabled="disabled"
+    >
+      {{ label }}
+    </b-button>
+    <confirmation-dialog
+      ref="modal"
+      :title="dialogTitle"
+      @ok="$emit('confirmed')"
+    >
+      <slot></slot>
+    </confirmation-dialog>
+  </div>
+</template>
+<script>
+import ConfirmationDialog from "./ConfirmationDialog.vue";
+
+export default {
+  name: "confirmation-button",
+  props: {
+    dialogTitle: {
+      type: String,
+      default: "Please confirm",
+    },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    label: {
+      type: String,
+      default: "Update",
+    },
+    variant: {
+      type: String,
+      default: "danger",
+    },
+  },
+  components: {
+    ConfirmationDialog,
+  },
+};
+</script>
+
+<style scoped>
+.confirmation-button {
+  display: inline-block;
+}
+</style>
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index ac905a9..b4bcc5e 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -4,6 +4,7 @@ import AutocompleteTextInput from "./components/AutocompleteTextInput.vue";
 import ClipboardCopyButton from "./components/ClipboardCopyButton.vue";
 import ClipboardCopyLink from "./components/ClipboardCopyLink.vue";
 import ComputeResourceName from "./components/ComputeResourceName";
+import ConfirmationButton from "./components/ConfirmationButton.vue";
 import ConfirmationDialog from "./components/ConfirmationDialog.vue";
 import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
@@ -47,6 +48,7 @@ const components = {
   ClipboardCopyButton,
   ClipboardCopyLink,
   ComputeResourceName,
+  ConfirmationButton,
   ConfirmationDialog,
   DataProductViewer,
   DeleteButton,

[airavata-django-portal] 03/13: AIRAVATA-3468 configuration of URLs for retrieving external IDP userinfo

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 8900d3203826b67af35bc4c476aaa919758318de
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 25 13:50:16 2021 -0400

    AIRAVATA-3468 configuration of URLs for retrieving external IDP userinfo
---
 django_airavata/apps/auth/backends.py | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/auth/backends.py b/django_airavata/apps/auth/backends.py
index 7202ec7..2197928 100644
--- a/django_airavata/apps/auth/backends.py
+++ b/django_airavata/apps/auth/backends.py
@@ -277,6 +277,17 @@ class KeycloakBackend(object):
 
     def _store_idp_userinfo(self, user, token, idp_alias):
         try:
+            idp_token_url = None
+            userinfo_url = None
+            for auth_option in settings.AUTHENTICATION_OPTIONS['external']:
+                if auth_option['idp_alias'] == idp_alias:
+                    idp_token_url = auth_option.get('idp_token_url')
+                    userinfo_url = auth_option.get('userinfo_url')
+                    break
+            if idp_token_url is None or userinfo_url is None:
+                logger.debug(f"idp_token_url and/or userinfo_url not set for {idp_alias} "
+                             "in AUTHENTICATION_OPTIONS, skipping retrieval of external IDP userinfo")
+                return
             access_token = token['access_token']
             logger.debug(f"access_token={access_token} for idp_alias={idp_alias}")
             # fetch the idp's token
@@ -284,10 +295,10 @@ class KeycloakBackend(object):
             # For the following to work, in Keycloak the IDP should have 'Store
             # Tokens' and 'Stored Tokens Readable' enabled and the user needs
             # the broker/read-token role
-            r = requests.get(f"https://iamdev.scigap.org/auth/realms/seagrid/broker/{idp_alias}/token", headers=headers)
+            r = requests.get(idp_token_url, headers=headers)
             idp_token = r.json()
             idp_headers = {'Authorization': f"Bearer {idp_token['access_token']}"}
-            r = requests.get("https://cilogon.org/oauth2/userinfo", headers=idp_headers)
+            r = requests.get(userinfo_url, headers=idp_headers)
             userinfo = r.json()
             logger.debug(f"userinfo={userinfo}")
 

[airavata-django-portal] 13/13: WIP

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 723cf770ea9b338fda20bff62f2ce2248aae24b4
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Aug 18 18:02:54 2021 -0400

    WIP
---
 .../static/django_airavata_api/js/models/User.js   |  1 +
 django_airavata/apps/auth/models.py                |  3 +-
 django_airavata/apps/auth/serializers.py           |  7 +++-
 .../js/components/UserProfileEditor.vue            | 38 +++++++++-------------
 .../js/containers/UserProfileContainer.vue         | 12 +++++--
 5 files changed, 34 insertions(+), 27 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/User.js b/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
index 648721a..e414444 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
@@ -8,6 +8,7 @@ const FIELDS = [
   "email",
   "pending_email_change",
   "complete",
+  "username_valid"
 ];
 
 export default class User extends BaseModel {
diff --git a/django_airavata/apps/auth/models.py b/django_airavata/apps/auth/models.py
index a7480ff..92ea9ea 100644
--- a/django_airavata/apps/auth/models.py
+++ b/django_airavata/apps/auth/models.py
@@ -1,10 +1,10 @@
 import uuid
 
 from django.conf import settings
+from django.core.exceptions import ValidationError
 from django.db import models
 
 from . import forms
-from django.core.exceptions import ValidationError
 
 VERIFY_EMAIL_TEMPLATE = 1
 NEW_USER_EMAIL_TEMPLATE = 2
@@ -75,6 +75,7 @@ class UserProfile(models.Model):
             validates = True
         except ValidationError:
             validates = False
+        # TODO: should be valid if matching an old email address too
         return (validates or (self.is_email_valid and self.user.email == self.user.username))
 
     @property
diff --git a/django_airavata/apps/auth/serializers.py b/django_airavata/apps/auth/serializers.py
index 29e2ef9..2eac92a 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -24,10 +24,12 @@ class UserSerializer(serializers.ModelSerializer):
 
     pending_email_change = serializers.SerializerMethodField()
     complete = serializers.SerializerMethodField()
+    username_valid = serializers.SerializerMethodField()
 
     class Meta:
         model = get_user_model()
-        fields = ['id', 'username', 'first_name', 'last_name', 'email', 'pending_email_change', 'complete']
+        fields = ['id', 'username', 'first_name', 'last_name', 'email',
+                  'pending_email_change', 'complete', 'username_valid']
 
     def get_pending_email_change(self, instance):
         request = self.context['request']
@@ -41,6 +43,9 @@ class UserSerializer(serializers.ModelSerializer):
     def get_complete(self, instance):
         return instance.user_profile.is_complete
 
+    def get_username_valid(self, instance):
+        return instance.user_profile.is_username_valid
+
     @atomic
     def update(self, instance, validated_data):
         request = self.context['request']
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index a59050b..39447d1 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -1,32 +1,24 @@
 <template>
   <b-card>
-    <b-form-group label="Username">
-      <b-form-input
-        v-model="$v.user.username.$model"
-        :disabled="true"
-        :state="validateState($v.user.username)"
-      />
-      <b-form-invalid-feedback v-if="!$v.user.username.emailOrMatchesRegex">
-        Username can only contain lowercase letters, numbers, underscores and
-        hyphens OR it can be the same as the email address. Only an
-        administrator can update your username to a valid value.
-      </b-form-invalid-feedback>
+    <!-- TODO: add help text that only administrators can change a user's username -->
+    <b-form-group label="Username" :disabled="true">
+      <b-form-input v-model="user.username" />
     </b-form-group>
-    <b-form-group label="First Name">
+    <b-form-group label="First Name" :disabled="disabled">
       <b-form-input
         v-model="$v.user.first_name.$model"
         @keydown.native.enter="save"
         :state="validateState($v.user.first_name)"
       />
     </b-form-group>
-    <b-form-group label="Last Name">
+    <b-form-group label="Last Name" :disabled="disabled">
       <b-form-input
         v-model="$v.user.last_name.$model"
         @keydown.native.enter="save"
         :state="validateState($v.user.last_name)"
       />
     </b-form-group>
-    <b-form-group label="Email">
+    <b-form-group label="Email" :disabled="disabled">
       <b-form-input
         v-model="$v.user.email.$model"
         @keydown.native.enter="save"
@@ -45,7 +37,7 @@
         ></b-alert
       >
     </b-form-group>
-    <b-button variant="primary" @click="save" :disabled="$v.$invalid"
+    <b-button variant="primary" @click="save" :disabled="$v.$invalid || disabled"
       >Save</b-button
     >
   </b-card>
@@ -55,7 +47,7 @@
 import { models } from "django-airavata-api";
 import { errors } from "django-airavata-common-ui";
 import { validationMixin } from "vuelidate";
-import { email, helpers, or, required, sameAs } from "vuelidate/lib/validators";
+import { email, required } from "vuelidate/lib/validators";
 
 export default {
   name: "user-profile-editor",
@@ -65,9 +57,15 @@ export default {
       type: models.User,
       required: true,
     },
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
   },
   created() {
-    this.$v.user.$touch();
+    if (!this.disabled) {
+      this.$v.user.$touch();
+    }
   },
   data() {
     return {
@@ -75,14 +73,8 @@ export default {
     };
   },
   validations() {
-    const usernameRegex = helpers.regex("username", /^[a-z0-9_-]+$/);
-    const emailOrMatchesRegex = or(usernameRegex, sameAs("email"));
     return {
       user: {
-        username: {
-          required,
-          emailOrMatchesRegex,
-        },
         first_name: {
           required,
         },
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 3a37faa..03f7b3f 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -1,16 +1,24 @@
 <template>
   <div>
     <h1 class="h4 mb-4">User Profile Editor</h1>
-    <b-alert :show="user && !user.complete"
+    <b-alert v-if="user && !user.username_valid" show variant="danger">
+      Unfortunately the username on your profile is invalid, which prevents
+      creating or updating your user profile. The administrators have been
+      notified and will be able to update your user account with a valid
+      username. Someone will notify you once your username has been updated to a
+      valid value.
+    </b-alert>
+    <b-alert v-else-if="user && !user.complete" show>
       >Please complete your user profile before continuing.</b-alert
     >
     <user-profile-editor
       v-if="user"
       v-model="user"
+      :disabled="!user.username_valid"
       @save="onSave"
       @resend-email-verification="resendEmailVerification"
     />
-    <b-link class="text-muted small" href="/workspace/dashboard"
+    <b-link v-if="user && user.complete" class="text-muted small" href="/workspace/dashboard"
       >Return to Dashboard</b-link
     >
   </div>

[airavata-django-portal] 07/13: AIRAVATA-3468 Allow /media/ in completeness check middleware (gateway logo)

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 530e1cbcc6fd6d21a9a9cf11039557cd22e284b6
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jul 7 13:55:59 2021 -0400

    AIRAVATA-3468 Allow /media/ in completeness check middleware (gateway logo)
---
 django_airavata/apps/auth/middleware.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/auth/middleware.py b/django_airavata/apps/auth/middleware.py
index 7921405..78ec89c 100644
--- a/django_airavata/apps/auth/middleware.py
+++ b/django_airavata/apps/auth/middleware.py
@@ -84,9 +84,9 @@ def user_profile_completeness_check(get_response):
             return get_response(request)
 
         if (not request.user.user_profile.is_complete and
-                request.path != reverse('django_airavata_auth:user_profile') and
-                request.path != reverse('django_airavata_auth:logout') and
-                request.META['HTTP_ACCEPT'] != 'application/json'):
+            request.path != reverse('django_airavata_auth:user_profile') and
+            request.path != reverse('django_airavata_auth:logout') and
+                request.META['HTTP_ACCEPT'] != 'application/json') and not request.path.startswith("/media/"):
             return redirect('django_airavata_auth:user_profile')
         else:
             return get_response(request)

[airavata-django-portal] 11/13: AIRAVATA-3468 Only redirect web page (Accepts: text/html) requests to complete profile

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 5801261a87d598d95de439869ffec5c7efdbbb19
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Aug 2 10:00:43 2021 -0400

    AIRAVATA-3468 Only redirect web page (Accepts: text/html) requests to complete profile
---
 django_airavata/apps/auth/middleware.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/auth/middleware.py b/django_airavata/apps/auth/middleware.py
index 78ec89c..ba49ec3 100644
--- a/django_airavata/apps/auth/middleware.py
+++ b/django_airavata/apps/auth/middleware.py
@@ -83,10 +83,13 @@ def user_profile_completeness_check(get_response):
         if not request.user.is_authenticated:
             return get_response(request)
 
+        allowed_paths = [
+            reverse('django_airavata_auth:user_profile'),
+            reverse('django_airavata_auth:logout'),
+        ]
         if (not request.user.user_profile.is_complete and
-            request.path != reverse('django_airavata_auth:user_profile') and
-            request.path != reverse('django_airavata_auth:logout') and
-                request.META['HTTP_ACCEPT'] != 'application/json') and not request.path.startswith("/media/"):
+            request.path not in allowed_paths and
+                'text/html' in request.META['HTTP_ACCEPT']):
             return redirect('django_airavata_auth:user_profile')
         else:
             return get_response(request)

[airavata-django-portal] 12/13: AIRAVATA-3468 Create user profile if it doesn't exist

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 5d765ac7cba7ec5b973640821b1b2b87adc16ab9
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Aug 2 10:01:54 2021 -0400

    AIRAVATA-3468 Create user profile if it doesn't exist
---
 django_airavata/apps/auth/serializers.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/django_airavata/apps/auth/serializers.py b/django_airavata/apps/auth/serializers.py
index 40723fd..29e2ef9 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -56,6 +56,12 @@ class UserSerializer(serializers.ModelSerializer):
         instance.save()
         # save in the user profile service too
         user_profile_client = request.profile_service['user_profile']
+        # Check if user profile exists and create it if not first. User Profile
+        # doesn't get created until the profile is complete, so it may not exist yet.
+        if not user_profile_client.doesUserExist(request.authz_token,
+                                                 request.user.username,
+                                                 settings.GATEWAY_ID):
+            user_profile_client.initializeUserProfile(request.authz_token)
         airavata_user_profile = user_profile_client.getUserProfileById(
             request.authz_token, request.user.username, settings.GATEWAY_ID)
         airavata_user_profile.firstName = instance.first_name

[airavata-django-portal] 04/13: AIRAVATA-3319 Admin API for updating a user's username

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 5adf6a7dc8cbaa5c69a521dfd1d7a35b741d9d10
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jul 1 10:11:54 2021 -0400

    AIRAVATA-3319 Admin API for updating a user's username
---
 django_airavata/apps/api/views.py             | 12 ++++++++++
 django_airavata/apps/auth/iam_admin_client.py | 34 +++++++++++++++++++++++++++
 2 files changed, 46 insertions(+)

diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 8e5c5a5..10ea16b 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -1780,6 +1780,18 @@ class IAMUserViewSet(mixins.RetrieveModelMixin,
                                            context={'request': request})
         return Response(serializer.data)
 
+    @action(methods=['put'], detail=True)
+    def update_username(self, request, user_id=None):
+        serializer = self.get_serializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        old_username = user_id
+        new_username = serializer.validated_data['userId']
+        iam_admin_client.update_username(old_username, new_username)
+        instance = self.get_instance(new_username)
+        serializer = self.serializer_class(instance=instance,
+                                           context={'request': request})
+        return Response(serializer.data)
+
     def _convert_user_profile(self, user_profile):
         user_profile_client = self.request.profile_service['user_profile']
         group_manager_client = self.request.profile_service['group_manager']
diff --git a/django_airavata/apps/auth/iam_admin_client.py b/django_airavata/apps/auth/iam_admin_client.py
index 113d2a8..3d2b29f 100644
--- a/django_airavata/apps/auth/iam_admin_client.py
+++ b/django_airavata/apps/auth/iam_admin_client.py
@@ -3,6 +3,10 @@ Wrapper around the IAM Admin Services client.
 """
 
 import logging
+from urllib.parse import urlparse
+
+import requests
+from django.conf import settings
 
 from django_airavata.utils import iamadmin_client_pool
 
@@ -61,3 +65,33 @@ def reset_user_password(username, new_password):
     authz_token = utils.get_service_account_authz_token()
     return iamadmin_client_pool.resetUserPassword(
         authz_token, username, new_password)
+
+
+def update_username(username, new_username):
+    # make sure that new_username is available
+    if not is_username_available(new_username):
+        raise Exception(f"Can't change username of {username} to {new_username} because it is not available")
+    # fetch user representation
+    authz_token = utils.get_service_account_authz_token()
+    headers = {'Authorization': f'Bearer {authz_token.accessToken}'}
+    parsed = urlparse(settings.KEYCLOAK_AUTHORIZE_URL)
+    r = requests.get(f"{parsed.scheme}://{parsed.netloc}/auth/admin/realms/{settings.GATEWAY_ID}/users",
+                     params={'username': username},
+                     headers=headers)
+    r.raise_for_status()
+    user_list = r.json()
+    user = None
+    # The users search finds partial matches. Loop to find the exact match.
+    for u in user_list:
+        if u['username'] == username:
+            user = u
+            break
+    if user is None:
+        raise Exception(f"Could not find user {username}")
+
+    # update username
+    user['username'] = new_username
+    r = requests.put(f"{parsed.scheme}://{parsed.netloc}/auth/admin/realms/{settings.GATEWAY_ID}/users/{user['id']}",
+                     json=user,
+                     headers=headers)
+    r.raise_for_status()

[airavata-django-portal] 01/13: AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 46577da613ead829bb1018ff12baec999f87254a
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jun 24 11:49:28 2021 -0400

    AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not
---
 django_airavata/apps/auth/backends.py              |  1 +
 django_airavata/apps/auth/middleware.py            | 26 ++++++++++++--
 django_airavata/apps/auth/models.py                | 40 +++++++++++++++++++---
 django_airavata/apps/auth/signals.py               | 17 +++++----
 .../js/components/UserProfileEditor.vue            | 18 ++++++++--
 django_airavata/settings.py                        |  1 +
 6 files changed, 87 insertions(+), 16 deletions(-)

diff --git a/django_airavata/apps/auth/backends.py b/django_airavata/apps/auth/backends.py
index d7e9176..96c57fd 100644
--- a/django_airavata/apps/auth/backends.py
+++ b/django_airavata/apps/auth/backends.py
@@ -230,6 +230,7 @@ class KeycloakBackend(object):
 
         # Update User model fields
         user = user_profile.user
+        user.username = username
         user.email = email
         user.first_name = first_name
         user.last_name = last_name
diff --git a/django_airavata/apps/auth/middleware.py b/django_airavata/apps/auth/middleware.py
index eb8a722..7921405 100644
--- a/django_airavata/apps/auth/middleware.py
+++ b/django_airavata/apps/auth/middleware.py
@@ -4,8 +4,10 @@ import logging
 
 from django.conf import settings
 from django.contrib.auth import logout
+from django.shortcuts import redirect
 
 from . import utils
+from django.urls import reverse
 
 log = logging.getLogger(__name__)
 
@@ -33,7 +35,10 @@ def gateway_groups_middleware(get_response):
     """Add 'is_gateway_admin' and 'is_read_only_gateway_admin' to request."""
     def middleware(request):
 
-        if not request.user.is_authenticated or not request.authz_token:
+        request.is_gateway_admin = False
+        request.is_read_only_gateway_admin = False
+
+        if not request.user.is_authenticated or not request.authz_token or not request.user.user_profile.is_complete:
             return get_response(request)
 
         try:
@@ -66,8 +71,23 @@ def gateway_groups_middleware(get_response):
         except Exception as e:
             log.warning("Failed to set is_gateway_admin, "
                         "is_read_only_gateway_admin for user", exc_info=e)
-            request.is_gateway_admin = False
-            request.is_read_only_gateway_admin = False
 
         return get_response(request)
     return middleware
+
+
+def user_profile_completeness_check(get_response):
+    """Check if user profile is complete and if not, redirect to user profile editor."""
+    def middleware(request):
+
+        if not request.user.is_authenticated:
+            return get_response(request)
+
+        if (not request.user.user_profile.is_complete and
+                request.path != reverse('django_airavata_auth:user_profile') and
+                request.path != reverse('django_airavata_auth:logout') and
+                request.META['HTTP_ACCEPT'] != 'application/json'):
+            return redirect('django_airavata_auth:user_profile')
+        else:
+            return get_response(request)
+    return middleware
diff --git a/django_airavata/apps/auth/models.py b/django_airavata/apps/auth/models.py
index c8c0da3..86ab451 100644
--- a/django_airavata/apps/auth/models.py
+++ b/django_airavata/apps/auth/models.py
@@ -3,6 +3,9 @@ import uuid
 from django.conf import settings
 from django.db import models
 
+from . import forms
+from django.core.exceptions import ValidationError
+
 VERIFY_EMAIL_TEMPLATE = 1
 NEW_USER_EMAIL_TEMPLATE = 2
 PASSWORD_RESET_EMAIL_TEMPLATE = 3
@@ -58,11 +61,38 @@ class UserProfile(models.Model):
 
     @property
     def is_complete(self):
-        # TODO: implement this to check if there are any missing fields on the
-        # User model (email, first_name, last_name) or if the username was is
-        # invalid (for example if defaulted to the IdP's 'sub' claim) or if
-        # there are any extra profile fields that are not valid
-        return False
+        return (self.is_username_valid and
+                self.is_first_name_valid and
+                self.is_last_name_valid and
+                self.is_email_valid)
+
+    @property
+    def is_username_valid(self):
+        # use forms.USERNAME_VALIDATOR with an exception when the username is
+        # equal to the email
+        try:
+            forms.USERNAME_VALIDATOR(self.user.username)
+            validates = True
+        except ValidationError:
+            validates = False
+        return (validates or (self.is_email_valid and self.user.email == self.user.username))
+
+    @property
+    def is_first_name_valid(self):
+        return self.is_non_empty(self.user.first_name)
+
+    @property
+    def is_last_name_valid(self):
+        return self.is_non_empty(self.user.last_name)
+
+    @property
+    def is_email_valid(self):
+        # Only checking for non-empty only; assumption is that email is verified
+        # before it is set or updated
+        return self.is_non_empty(self.user.email)
+
+    def is_non_empty(self, value: str):
+        return value is not None and value.strip() != ""
 
 
 class UserInfo(models.Model):
diff --git a/django_airavata/apps/auth/signals.py b/django_airavata/apps/auth/signals.py
index eb12e17..466e20b 100644
--- a/django_airavata/apps/auth/signals.py
+++ b/django_airavata/apps/auth/signals.py
@@ -42,11 +42,16 @@ def initialize_user_profile(sender, request, user, **kwargs):
         if not user_profile_client_pool.doesUserExist(request.authz_token,
                                                       user.username,
                                                       settings.GATEWAY_ID):
-            user_profile_client_pool.initializeUserProfile(request.authz_token)
-            log.info("initialized user profile for {}".format(user.username))
-            # Since user profile created, inform admins of new user
-            utils.send_new_user_email(
-                request, user.username, user.email, user.first_name, user.last_name)
-            log.info("sent new user email for user {}".format(user.username))
+            if user.user_profile.is_complete:
+                user_profile_client_pool.initializeUserProfile(request.authz_token)
+                log.info("initialized user profile for {}".format(user.username))
+                # Since user profile created, inform admins of new user
+                utils.send_new_user_email(
+                    request, user.username, user.email, user.first_name, user.last_name)
+                log.info("sent new user email for user {}".format(user.username))
+            else:
+                log.info(f"user profile not complete for {user.username}, "
+                         "skipping initializing Airavata user profile")
+
     else:
         log.warning(f"Logged in user {user.username} has no access token")
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index 506114c..b6b31fe 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -1,7 +1,15 @@
 <template>
   <b-card>
     <b-form-group label="Username">
-      <b-form-input disabled :value="user.username" />
+      <b-form-input
+        v-model="$v.user.username.$model"
+        @keydown.native.enter="save"
+        :state="validateState($v.user.username)"
+      />
+      <b-form-invalid-feedback v-if="!$v.user.username.emailOrMatchesRegex">
+        Username can only contain lowercase letters, numbers, underscores and
+        hyphens OR it can be the same as the email address.
+      </b-form-invalid-feedback>
     </b-form-group>
     <b-form-group label="First Name">
       <b-form-input
@@ -46,7 +54,7 @@
 import { models } from "django-airavata-api";
 import { errors } from "django-airavata-common-ui";
 import { validationMixin } from "vuelidate";
-import { email, required } from "vuelidate/lib/validators";
+import { email, helpers, or, required, sameAs } from "vuelidate/lib/validators";
 
 export default {
   name: "user-profile-editor",
@@ -63,8 +71,14 @@ export default {
     };
   },
   validations() {
+    const usernameRegex = helpers.regex("username", /^[a-z0-9_-]+$/);
+    const emailOrMatchesRegex = or(usernameRegex, sameAs('email'));
     return {
       user: {
+        username: {
+          required,
+          emailOrMatchesRegex,
+        },
         first_name: {
           required,
         },
diff --git a/django_airavata/settings.py b/django_airavata/settings.py
index 3bae1dd..ad82836 100644
--- a/django_airavata/settings.py
+++ b/django_airavata/settings.py
@@ -130,6 +130,7 @@ MIDDLEWARE = [
     'django_airavata.apps.auth.middleware.gateway_groups_middleware',
     # Wagtail related middleware
     'wagtail.contrib.redirects.middleware.RedirectMiddleware',
+    'django_airavata.apps.auth.middleware.user_profile_completeness_check',
 ]
 
 ROOT_URLCONF = 'django_airavata.urls'

[airavata-django-portal] 08/13: AIRAVATA-3468 Inform user that they must complete profile

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 4d910c99ddb05be1431e271a417e1aca9458f9eb
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Mon Jul 26 14:58:38 2021 -0400

    AIRAVATA-3468 Inform user that they must complete profile
---
 .../apps/api/static/django_airavata_api/js/models/User.js      | 10 +++++++++-
 django_airavata/apps/auth/serializers.py                       |  6 +++++-
 .../js/containers/UserProfileContainer.vue                     |  3 +++
 3 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/User.js b/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
index 53cbf79..648721a 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/User.js
@@ -1,6 +1,14 @@
 import BaseModel from "./BaseModel";
 
-const FIELDS = ["id", "username", "first_name", "last_name", "email", "pending_email_change"];
+const FIELDS = [
+  "id",
+  "username",
+  "first_name",
+  "last_name",
+  "email",
+  "pending_email_change",
+  "complete",
+];
 
 export default class User extends BaseModel {
   constructor(data = {}) {
diff --git a/django_airavata/apps/auth/serializers.py b/django_airavata/apps/auth/serializers.py
index f12854e..40723fd 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -23,10 +23,11 @@ class PendingEmailChangeSerializer(serializers.ModelSerializer):
 class UserSerializer(serializers.ModelSerializer):
 
     pending_email_change = serializers.SerializerMethodField()
+    complete = serializers.SerializerMethodField()
 
     class Meta:
         model = get_user_model()
-        fields = ['id', 'username', 'first_name', 'last_name', 'email', 'pending_email_change']
+        fields = ['id', 'username', 'first_name', 'last_name', 'email', 'pending_email_change', 'complete']
 
     def get_pending_email_change(self, instance):
         request = self.context['request']
@@ -37,6 +38,9 @@ class UserSerializer(serializers.ModelSerializer):
         else:
             return None
 
+    def get_complete(self, instance):
+        return instance.user_profile.is_complete
+
     @atomic
     def update(self, instance, validated_data):
         request = self.context['request']
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index d6eebce..da4e001 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -1,6 +1,9 @@
 <template>
   <div>
     <h1 class="h4 mb-4">User Profile Editor</h1>
+    <b-alert :show="user && !user.complete"
+      >Please complete your user profile before continuing.</b-alert
+    >
     <user-profile-editor
       v-if="user"
       v-model="user"

[airavata-django-portal] 10/13: AIRAVATA-3468 Add link for navigating back to the dashboard

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 8648a4c1143b99e0009788ca44feb69d5b6c1983
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jul 28 14:28:28 2021 -0400

    AIRAVATA-3468 Add link for navigating back to the dashboard
---
 .../static/django_airavata_auth/js/components/UserProfileEditor.vue    | 2 +-
 .../static/django_airavata_auth/js/containers/UserProfileContainer.vue | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index 23b9104..a59050b 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -76,7 +76,7 @@ export default {
   },
   validations() {
     const usernameRegex = helpers.regex("username", /^[a-z0-9_-]+$/);
-    const emailOrMatchesRegex = or(usernameRegex, sameAs('email'));
+    const emailOrMatchesRegex = or(usernameRegex, sameAs("email"));
     return {
       user: {
         username: {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index da4e001..3a37faa 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -10,6 +10,9 @@
       @save="onSave"
       @resend-email-verification="resendEmailVerification"
     />
+    <b-link class="text-muted small" href="/workspace/dashboard"
+      >Return to Dashboard</b-link
+    >
   </div>
 </template>
 

[airavata-django-portal] 02/13: AIRAVATA-3468 Store IDP userinfo

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 7b0e99bdc0c9d5f56f865a7b1643e0c15ee08858
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 25 13:39:14 2021 -0400

    AIRAVATA-3468 Store IDP userinfo
    
    This is mostly for debugging purposes, to see what is and isn't included in CILogon userinfo.
---
 django_airavata/apps/auth/backends.py              | 34 +++++++++++++++++-
 .../auth/migrations/0009_auto_20210625_1725.py     | 41 ++++++++++++++++++++++
 django_airavata/apps/auth/models.py                | 17 +++++++++
 django_airavata/apps/auth/views.py                 |  5 +--
 4 files changed, 94 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/auth/backends.py b/django_airavata/apps/auth/backends.py
index 96c57fd..7202ec7 100644
--- a/django_airavata/apps/auth/backends.py
+++ b/django_airavata/apps/auth/backends.py
@@ -29,7 +29,8 @@ class KeycloakBackend(object):
                      request=None,
                      username=None,
                      password=None,
-                     refresh_token=None):
+                     refresh_token=None,
+                     idp_alias=None):
         try:
             user = None
             access_token = None
@@ -75,6 +76,9 @@ class KeycloakBackend(object):
                     request)
                 self._process_token(request, token)
                 user = self._process_userinfo(request, userinfo)
+                if idp_alias is not None:
+                    self._store_idp_userinfo(user, token, idp_alias)
+                # TODO: if idp_alias, add idp userinfo too
                 access_token = token['access_token']
             # authz_token_middleware has already run, so must manually add
             # the `request.authz_token` attribute
@@ -270,3 +274,31 @@ class KeycloakBackend(object):
                 user_profile.save()
                 user_profile.userinfo_set.create(claim='sub', value=sub)
                 return user
+
+    def _store_idp_userinfo(self, user, token, idp_alias):
+        try:
+            access_token = token['access_token']
+            logger.debug(f"access_token={access_token} for idp_alias={idp_alias}")
+            # fetch the idp's token
+            headers = {'Authorization': f'Bearer {access_token}'}
+            # For the following to work, in Keycloak the IDP should have 'Store
+            # Tokens' and 'Stored Tokens Readable' enabled and the user needs
+            # the broker/read-token role
+            r = requests.get(f"https://iamdev.scigap.org/auth/realms/seagrid/broker/{idp_alias}/token", headers=headers)
+            idp_token = r.json()
+            idp_headers = {'Authorization': f"Bearer {idp_token['access_token']}"}
+            r = requests.get("https://cilogon.org/oauth2/userinfo", headers=idp_headers)
+            userinfo = r.json()
+            logger.debug(f"userinfo={userinfo}")
+
+            # Save the idp user info claims
+            user_profile = user.user_profile
+            for (claim, value) in userinfo.items():
+                if user_profile.idp_userinfo.filter(idp_alias=idp_alias, claim=claim).exists():
+                    userinfo_claim = user_profile.idp_userinfo.get(idp_alias=idp_alias, claim=claim)
+                    userinfo_claim.value = value
+                    userinfo_claim.save()
+                else:
+                    user_profile.idp_userinfo.create(idp_alias=idp_alias, claim=claim, value=value)
+        except Exception:
+            logger.exception(f"Failed to store IDP userinfo for {user.username} from IDP {idp_alias}")
diff --git a/django_airavata/apps/auth/migrations/0009_auto_20210625_1725.py b/django_airavata/apps/auth/migrations/0009_auto_20210625_1725.py
new file mode 100644
index 0000000..1d38199
--- /dev/null
+++ b/django_airavata/apps/auth/migrations/0009_auto_20210625_1725.py
@@ -0,0 +1,41 @@
+# Generated by Django 2.2.23 on 2021-06-25 17:25
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('django_airavata_auth', '0008_auto_20210422_1838'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='userinfo',
+            name='created_date',
+            field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='userinfo',
+            name='updated_date',
+            field=models.DateTimeField(auto_now=True),
+        ),
+        migrations.CreateModel(
+            name='IDPUserInfo',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('idp_alias', models.CharField(max_length=64)),
+                ('claim', models.CharField(max_length=64)),
+                ('value', models.CharField(max_length=255)),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+                ('updated_date', models.DateTimeField(auto_now=True)),
+                ('user_profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='idp_userinfo', to='django_airavata_auth.UserProfile')),
+            ],
+            options={
+                'unique_together': {('user_profile', 'claim', 'idp_alias')},
+            },
+        ),
+    ]
diff --git a/django_airavata/apps/auth/models.py b/django_airavata/apps/auth/models.py
index 86ab451..a7480ff 100644
--- a/django_airavata/apps/auth/models.py
+++ b/django_airavata/apps/auth/models.py
@@ -99,6 +99,8 @@ class UserInfo(models.Model):
     claim = models.CharField(max_length=64)
     value = models.CharField(max_length=255)
     user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
+    created_date = models.DateTimeField(auto_now_add=True)
+    updated_date = models.DateTimeField(auto_now=True)
 
     class Meta:
         unique_together = ['user_profile', 'claim']
@@ -107,6 +109,21 @@ class UserInfo(models.Model):
         return f"{self.claim}={self.value}"
 
 
+class IDPUserInfo(models.Model):
+    idp_alias = models.CharField(max_length=64)
+    claim = models.CharField(max_length=64)
+    value = models.CharField(max_length=255)
+    user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name="idp_userinfo")
+    created_date = models.DateTimeField(auto_now_add=True)
+    updated_date = models.DateTimeField(auto_now=True)
+
+    class Meta:
+        unique_together = ['user_profile', 'claim', 'idp_alias']
+
+    def __str__(self):
+        return f"{self.idp_alias}: {self.claim}={self.value}"
+
+
 class PendingEmailChange(models.Model):
     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
     email_address = models.EmailField()
diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index 977f604..2c4f634 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -146,7 +146,9 @@ def start_logout(request):
 def callback(request):
     try:
         login_desktop = request.GET.get('login_desktop', "false") == "true"
-        user = authenticate(request=request)
+        idp_alias = request.GET.get('idp_alias')
+        user = authenticate(request=request, idp_alias=idp_alias)
+
         if user is not None:
             login(request, user)
             if login_desktop:
@@ -161,7 +163,6 @@ def callback(request):
         messages.error(
             request,
             "Failed to process OAuth2 callback: {}".format(str(err)))
-        idp_alias = request.GET.get('idp_alias')
         if login_desktop:
             return _create_login_desktop_failed_response(
                 request, idp_alias=idp_alias)