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)