From: Jeremy Mordkoff Date: Sun, 1 Oct 2017 01:42:44 +0000 (-0400) Subject: update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try X-Git-Tag: v3.0.0rc2^0 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FUI.git;a=commitdiff_plain;h=03156e335275de1dafbc2a816e98006afdf249bf update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try Signed-off-by: Jeremy Mordkoff Change-Id: Ib11aa03a2eff5a53c808342508a5d7bee7b202b8 --- diff --git a/.gitignore b/.gitignore index 40934df14..6747eae45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ .DS_Store/ .DS_Store +._.DS_Store err.log out.log node_modules/ npm-debug.log fixtures/ .build +.fuse_* diff --git a/BUILD.sh b/BUILD.sh new file mode 100755 index 000000000..e244b7c7b --- /dev/null +++ b/BUILD.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# Copyright 2016,2017 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author(s): Jeremy Mordkoff, Lezz Giles +# Creation Date: 08/29/2016 +# +# + +# BUILD.sh +# +# This is a top-level build script for OSM SO or UI +# +# Arguments and options: use -h or --help +# +# dependencies -- requires sudo rights + +MODULE=UI + +# Defensive bash programming flags +set -o errexit # Exit on any error +trap 'echo ERROR: Command failed: \"$BASH_COMMAND\"' ERR +set -o nounset # Expanding an unset variable is an error. Variables must be + # set before they can be used. + +############################################################################### +# Options and arguments + +# There +params="$(getopt -o h -l install,help --name "$0" -- "$@")" +if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi + +eval set -- $params + +installFromPackages=false + +while true; do + case "$1" in + --install) installFromPackages=true; shift;; + -h|--help) + echo + echo "NAME:" + echo " $0" + echo + echo "SYNOPSIS:" + echo " $0 -h|--help" + echo " $0 [--install] [PLATFORM_REPOSITORY] [PLATFORM_VERSION]" + echo + echo "DESCRIPTION:" + echo " Prepare current system to run $MODULE. By default, the system" + echo " is set up to support building $MODULE; optionally, " + echo " $MODULE can be installed from a Debian package repository." + echo + echo " --install: install $MODULE from package" + echo " PLATFORM_REPOSITORY (optional): name of the RIFT.ware repository." + echo " PLATFORM_VERSION (optional): version of the platform packages to be installed." + echo + exit 0;; + --) shift; break;; + *) echo "Not implemented: $1" >&2; exit 1;; + esac +done + +# Turn this on after handling options, so the output doesn't get cluttered. +set -x # Print commands before executing them + +############################################################################### +# Set up repo and version + +PLATFORM_REPOSITORY=${1:-osm-rbac} +PLATFORM_VERSION=${2:-5.1.3.9999.70283} + +############################################################################### +# Main block + +# must be run from the top of a workspace +cd $(dirname $0) + +# enable the right repos +curl http://repos.riftio.com/public/xenial-riftware-public-key | sudo apt-key add - + +# always use the same file name so that updates will overwrite rather than enable a second repo +sudo curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/${PLATFORM_REPOSITORY}/ +sudo apt-get update + +sudo apt install -y --allow-downgrades rw.tools-container-tools=${PLATFORM_VERSION} rw.tools-scripts=${PLATFORM_VERSION} + +if $installFromPackages; then + + # Install module and platform from packages + sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION} + +else + + # Install environment to build module + sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE-dev --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION} + + # Build and install module + make -j16 + sudo make install + +fi + +if [[ $MODULE == SO ]]; then + echo "Creating Service ...." + sudo /usr/rift/bin/create_launchpad_service +fi + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c9b5766c..8a21f2fd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ cmake_minimum_required(VERSION 2.8) # DO NOT add any code before this and DO NOT # include this file anywhere else ## -include(rift_submodule) +include(rift_submodule NO_POLICY_SCOPE) ## # Submodule specific includes will go here, @@ -53,6 +53,15 @@ rift_add_subdirs( ${subdirs} ) +## +# Only skyquake package contains anything +## +rift_set_component_package_fields( + skyquake + DESCRIPTION "RIFT.ware UI" + POST_INSTALL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/postinst" + ) + ## # This macro adds targets for documentaion, unittests, code coverage and packaging ## diff --git a/Dockerfile b/Dockerfile index 40dc9227f..d576e2ff8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,13 @@ FROM ubuntu:16.04 -RUN apt-get update && apt-get -y install python3 curl build-essential +RUN apt-get update && apt-get -y install python3 curl build-essential apt-transport-https sudo RUN curl http://repos.riftio.com/public/xenial-riftware-public-key | apt-key add - && \ - curl -o /etc/apt/sources.list.d/OSM.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \ + curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \ apt-get update && \ - apt-get -y install rw.toolchain-rwbase \ - rw.toolchain-rwtoolchain \ - rw.core.mgmt-mgmt \ - rw.core.util-util \ - rw.core.rwvx-rwvx \ - rw.core.rwvx-rwdts \ - rw.automation.core-RWAUTO \ - rw.tools-container-tools \ - rw.tools-scripts \ - python-cinderclient \ - libxml2-dev \ - libxslt-dev + apt-get -y install \ + rw.tools-container-tools=5.2.0.0.71033 \ + rw.tools-scripts=5.2.0.0.71033 -RUN /usr/rift/container_tools/mkcontainer --modes build --modes ext --repo OSM +RUN /usr/rift/container_tools/mkcontainer --modes UI-dev --repo OSM --rw-version 5.2.0.0.71033 -RUN chmod 777 /usr/rift /usr/rift/usr/share - -RUN rm -rf /tmp/npm-cache +RUN chmod 777 /usr/rift diff --git a/Jenkinsfile b/Jenkinsfile index 99cb40523..ceb06c31f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -29,4 +29,3 @@ node('docker') { params.GERRIT_PATCHSET_REVISION, params.TEST_INSTALL, params.ARTIFACTORY_SERVER) -} diff --git a/manifest/LICENSE b/manifest/LICENSE index e69de29bb..ab4a98190 100644 --- a/manifest/LICENSE +++ b/manifest/LICENSE @@ -0,0 +1 @@ +ho diff --git a/postinst b/postinst new file mode 100755 index 000000000..602b11041 --- /dev/null +++ b/postinst @@ -0,0 +1,21 @@ +#!/bin/bash + +# +# Copyright 2016 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Post-install script for packaging + +/usr/rift/usr/share/rw.ui/skyquake/scripts/handle_plugin_node_modules diff --git a/python-osmclient/.gitignore b/python-osmclient/.gitignore deleted file mode 100644 index e09c6a053..000000000 --- a/python-osmclient/.gitignore +++ /dev/null @@ -1,90 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -deb_dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject diff --git a/python-osmclient/Makefile b/python-osmclient/Makefile deleted file mode 100644 index 1a2e44884..000000000 --- a/python-osmclient/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -package: - @python setup.py --command-packages=stdeb.command bdist_deb > /dev/null 2>&1 diff --git a/python-osmclient/README.md b/python-osmclient/README.md deleted file mode 100644 index 8d41f2f2b..000000000 --- a/python-osmclient/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# python-osmclient -A python client for osm orchestration - -# Installation - -## Install dependencies -```bash -sudo apt-get install python-dev libcurl4-gnutls-dev python-pip libgnutls-dev python-prettytable   -sudo pip install pycurl -``` - -# Setup -Set the OSM_HOSTNAME variable to the host of the osm server. - -Example -```bash -localhost$ export OSM_HOSTNAME=:8008 -``` - -# Examples - -## upload vnfd -```bash -localhost$ osm upload-package ubuntu_xenial_vnf.tar.gz -{'transaction_id': 'ec12af77-1b91-4c84-b233-60f2c2c16d14'} -localhost$ osm vnfd-list -+--------------------+--------------------+ -| vnfd name | id | -+--------------------+--------------------+ -| ubuntu_xenial_vnfd | ubuntu_xenial_vnfd | -+--------------------+--------------------+ -``` - -## upload nsd -```bash -localhost$ osm upload-package ubuntu_xenial_ns.tar.gz -{'transaction_id': 'b560c9cb-43e1-49ef-a2da-af7aab24ce9d'} -localhost$ osm nsd-list -+-------------------+-------------------+ -| nsd name | id | -+-------------------+-------------------+ -| ubuntu_xenial_nsd | ubuntu_xenial_nsd | -+-------------------+-------------------+ -``` -## vim-list - -```bash -localhost$ osm vim-list -+-------------+-----------------+--------------------------------------+ -| ro-account | datacenter name | uuid | -+-------------+-----------------+--------------------------------------+ -| osmopenmano | openstack-site | 2ea04690-0e4a-11e7-89bc-00163e59ff0c | -+-------------+-----------------+--------------------------------------+ -``` - - -## instantiate ns -```bash -localhost$ osm ns-create ubuntu_xenial_nsd testns openstack-site -{'success': ''} -localhost$ osm ns-list -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -| ns instance name | id | catalog name | operational status | config status | -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -| testns | 6b0d2906-13d4-11e7-aa01-b8ac6f7d0c77 | ubuntu_xenial_nsd | running | configured | -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -``` - -# Bash Completion -python-osmclient uses [click](http://click.pocoo.org/5/). You can setup bash completion by putting this in your .bashrc: - - eval "$(_OSM_COMPLETE=source osm)" - diff --git a/python-osmclient/osmclient/__init__.py b/python-osmclient/osmclient/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/common/OsmAPI.py b/python-osmclient/osmclient/common/OsmAPI.py deleted file mode 100644 index f85e87250..000000000 --- a/python-osmclient/osmclient/common/OsmAPI.py +++ /dev/null @@ -1,436 +0,0 @@ -from io import BytesIO -import pycurl -import json -import pprint -import uuid -from prettytable import PrettyTable -import time - -class OsmAPI(): - def __init__(self,host,upload_port=8443): - if host is None: - raise Exception('missing host specifier') - - self._user='admin' - self._password='admin' - self._host=host - self._upload_port=upload_port - self._url = 'https://{}/'.format(self._host) - self._upload_url = 'https://{}:{}/'.format(self._host.split(':')[0],self._upload_port) - - def get_curl_cmd(self,url): - curl_cmd = pycurl.Curl() - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.URL, self._url + url ) - curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0) - curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0) - curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password)) - curl_cmd.setopt(pycurl.HTTPHEADER, ['Accept: application/vnd.yand.data+json','Content-Type": "application/vnd.yang.data+json']) - return curl_cmd - - def get_curl_upload_cmd(self,filename): - curl_cmd = pycurl.Curl() - curl_cmd.setopt(pycurl.HTTPPOST,[(('package',(pycurl.FORM_FILE,filename)))]) - curl_cmd.setopt(pycurl.URL, self._upload_url + 'composer/upload?api_server=https://localhost&upload_server=https://' + self._host.split(':')[0]) - curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0) - curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0) - curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password)) - return curl_cmd - - def get_ns_opdata(self,id): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/operational/ns-instance-opdata/nsr/{}?deep'.format(id)) - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - return json.loads(data.getvalue().decode()) - return None - - def get_ns_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return {'nsd:nsd': []} - - def get_config_agents(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - if 'rw-config-agent:config-agent' in resp: - return resp['rw-config-agent:config-agent']['account'] - return list() - - def delete_config_agent(self,name): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent/account/'+name) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def add_config_agent(self,name,account_type,server,user,secret): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - - postdata={} - postdata['account'] = list() - - account={} - account['name'] = name - account['account-type'] = account_type - account['juju'] = {} - account['juju']['user'] = user - account['juju']['secret'] = secret - account['juju']['ip-address'] = server - postdata['account'].append(account) - - jsondata=json.dumps(postdata) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def get_ns_instance_list(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/ns-instance-config') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - return resp['nsr:ns-instance-config'] - - def get_vnf_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return {'vnfd:vnfd': []} - - def get_vcs_info(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/operational/vcs/info') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp['rw-base:info']['components']['component_info'] - return list() - - def get_vnfr_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/vnfr-catalog/vnfr') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return None - - def get_vnf(self,vnf_name): - vnfs=self.get_vnfr_catalog() - for vnf in vnfs['vnfr:vnfr']: - if vnf_name == vnf['name']: - return vnf - return None - - def get_vnf_monitoring(self,vnf_name): - vnf=self.get_vnf(vnf_name) - if vnf is not None: - if 'monitoring-param' in vnf: - return vnf['monitoring-param'] - return None - - def get_ns_monitoring(self,ns_name): - ns=self.get_ns(ns_name) - if ns is None: - raise Exception('cannot find ns {}'.format(ns_name)) - - vnfs=self.get_vnfr_catalog() - if vnfs is None: - return None - mon_list={} - for vnf in vnfs['vnfr:vnfr']: - if ns['id'] == vnf['nsr-id-ref']: - if 'monitoring-param' in vnf: - mon_list[vnf['name']] = vnf['monitoring-param'] - - return mon_list - - def list_key_pair(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/config/key-pair?deep') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - if 'nsr:key-pair' in resp: - return resp['nsr:key-pair'] - return list() - - def list_ns_catalog(self): - resp = self.get_ns_catalog() - table=PrettyTable(['nsd name','id']) - for ns in resp['nsd:nsd']: - table.add_row([ns['name'],ns['id']]) - table.align='l' - print(table) - - def list_ns_instance(self): - resp = self.get_ns_instance_list() - table=PrettyTable(['ns instance name','id','catalog name','operational status','config status']) - if 'nsr' in resp: - for ns in resp['nsr']: - nsopdata=self.get_ns_opdata(ns['id']) - nsr=nsopdata['nsr:nsr'] - table.add_row([nsr['name-ref'],nsr['ns-instance-config-ref'],nsr['nsd-name-ref'],nsr['operational-status'],nsr['config-status']]) - table.align='l' - print(table) - - def get_nsd(self,name): - resp = self.get_ns_catalog() - for ns in resp['nsd:nsd']: - if name == ns['name']: - return ns - return None - - def get_vnfd(self,name): - resp = self.get_vnf_catalog() - for vnf in resp['vnfd:vnfd']: - if name == vnf['name']: - return vnf - return None - - def get_ns(self,name): - resp=self.get_ns_instance_list() - for ns in resp['nsr']: - if name == ns['name']: - return ns - return None - - def instantiate_ns(self,nsd_name,nsr_name,account,vim_network_prefix=None,ssh_keys=None,description='default description',admin_status='ENABLED'): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - - postdata={} - postdata['nsr'] = list() - nsr={} - nsr['id']=str(uuid.uuid1()) - - nsd=self.get_nsd(nsd_name) - if nsd is None: - raise Exception('cannot find nsd {}'.format(nsd_name)) - - datacenter=self.get_datacenter(account) - if datacenter is None: - raise Exception('cannot find datacenter account {}'.format(account)) - - nsr['nsd']=nsd - nsr['name']=nsr_name - nsr['short-name']=nsr_name - nsr['description']=description - nsr['admin-status']=admin_status - nsr['om-datacenter']=datacenter['uuid'] - - if ssh_keys is not None: - # ssh_keys is comma separate list - ssh_keys_format=[] - for key in ssh_keys.split(','): - ssh_keys_format.append({'key-pair-ref':key}) - - nsr['ssh-authorized-key']=ssh_keys_format - - if vim_network_prefix is not None: - for index,vld in enumerate(nsr['nsd']['vld']): - network_name = vld['name'] - nsr['nsd']['vld'][index]['vim-network-name'] = '{}-{}'.format(vim_network_prefix,network_name) - - postdata['nsr'].append(nsr) - jsondata=json.dumps(postdata) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def delete_nsd(self,nsd_name): - nsd=self.get_nsd(nsd_name) - if nsd is None: - raise Exception('cannot find nsd {}'.format(nsd_name)) - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd/'+nsd['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def delete_vnfd(self,vnfd_name): - vnfd=self.get_vnfd(vnfd_name) - if vnfd is None: - raise Exception('cannot find vnfd {}'.format(vnfd_name)) - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd/'+vnfd['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def terminate_ns(self,ns_name): - ns=self.get_ns(ns_name) - if ns is None: - raise Exception('cannot find ns {}'.format(ns_name)) - - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr/'+ns['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def upload_package(self,filename): - data = BytesIO() - curl_cmd=self.get_curl_upload_cmd(filename) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def add_vim_account(self,name,user_name,secret,auth_url,tenant,mgmt_network,float_ip_pool,account_type='openstack'): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/cloud') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - vim_account={} - vim_account['account']={} - - vim_account['account']['name'] = name - vim_account['account']['account-type'] = account_type - vim_account['account'][account_type] = {} - vim_account['account'][account_type]['key'] = user_name - vim_account['account'][account_type]['secret'] = secret - vim_account['account'][account_type]['auth_url'] = auth_url - vim_account['account'][account_type]['tenant'] = tenant - vim_account['account'][account_type]['mgmt-network'] = mgmt_network - vim_account['account'][account_type]['floating-ip-pool'] = float_ip_pool - - jsondata=json.dumps(vim_account) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def list_vim_accounts(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - datacenters=resp['rw-launchpad:datacenters'] - if 'ro-accounts' in datacenters: - return datacenters['ro-accounts'] - return list() - - def get_datacenter(self,name): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - datacenters=resp['rw-launchpad:datacenters'] - if 'ro-accounts' in datacenters: - for roaccount in datacenters['ro-accounts']: - if not 'datacenters' in roaccount: - continue - for datacenter in roaccount['datacenters']: - if datacenter['name'] == name: - return datacenter - return None - - def show_ns(self,ns_name): - resp = self.get_ns_instance_list() - table=PrettyTable(['attribute','value']) - - if 'nsr' in resp: - for ns in resp['nsr']: - if ns_name == ns['name']: - # dump ns config - for k,v in ns.items(): - table.add_row([k,json.dumps(v,indent=2)]) - - nsopdata=self.get_ns_opdata(ns['id']) - nsr_optdata=nsopdata['nsr:nsr'] - for k,v in nsr_optdata.items(): - table.add_row([k,json.dumps(v,indent=2)]) - table.align='l' - print(table) - - def show_ns_scaling(self,ns_name): - resp = self.get_ns_instance_list() - - table=PrettyTable(['instance-id','operational status','create-time','vnfr ids']) - - if 'nsr' in resp: - for ns in resp['nsr']: - if ns_name == ns['name']: - nsopdata=self.get_ns_opdata(ns['id']) - scaling_records=nsopdata['nsr:nsr']['scaling-group-record'] - for record in scaling_records: - if 'instance' in record: - instances=record['instance'] - for inst in instances: - table.add_row([inst['instance-id'],inst['op-status'],time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(inst['create-time'])),inst['vnfrs']]) - table.align='l' - print(table) - - def list_vnfr(self): - return self.get_vnfr_catalog() - - def list_vnf_catalog(self): - resp = self.get_vnf_catalog() - table=PrettyTable(['vnfd name','id']) - for ns in resp['vnfd:vnfd']: - table.add_row([ns['name'],ns['id']]) - table.align='l' - print(table) diff --git a/python-osmclient/osmclient/common/__init__.py b/python-osmclient/osmclient/common/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/scripts/__init__.py b/python-osmclient/osmclient/scripts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/scripts/osm.py b/python-osmclient/osmclient/scripts/osm.py deleted file mode 100755 index d74580cfb..000000000 --- a/python-osmclient/osmclient/scripts/osm.py +++ /dev/null @@ -1,191 +0,0 @@ -import click -from osmclient.common import OsmAPI -from prettytable import PrettyTable -import pprint -import textwrap - -@click.group() -@click.option('--hostname',default=None,envvar='OSM_HOSTNAME',help='hostname of server. Also can set OSM_HOSTNAME in environment') -@click.pass_context -def cli(ctx,hostname): - if hostname is None: - print("either hostname option or OSM_HOSTNAME environment variable needs to be specified") - exit(1) - ctx.obj=OsmAPI.OsmAPI(hostname) - -@cli.command(name='ns-list') -@click.pass_context -def ns_list(ctx): - ctx.obj.list_ns_instance() - -@cli.command(name='nsd-list') -@click.pass_context -def nsd_list(ctx): - ctx.obj.list_ns_catalog() - -@cli.command(name='vnfd-list') -@click.pass_context -def vnfd_list(ctx): - ctx.obj.list_vnf_catalog() - -@cli.command(name='vnf-list') -@click.pass_context -def vnf_list(ctx): - resp=ctx.obj.list_vnfr() - table=PrettyTable(['vnf name','id','operational status','config Status','mgmt interface','nsr id']) - if resp is not None: - for vnfr in resp['vnfr:vnfr']: - if not 'mgmt-interface' in vnfr: - vnfr['mgmt-interface'] = {} - vnfr['mgmt-interface']['ip-address'] = None - table.add_row([vnfr['name'],vnfr['id'],vnfr['operational-status'],vnfr['config-status'],vnfr['mgmt-interface']['ip-address'],vnfr['nsr-id-ref']]) - table.align='l' - print(table) - -@cli.command(name='vnf-monitoring-show') -@click.argument('vnf_name') -@click.pass_context -def vnf_monitoring_show(ctx,vnf_name): - resp=ctx.obj.get_vnf_monitoring(vnf_name) - table=PrettyTable(['vnf name','monitoring name','value','units']) - if resp is not None: - for monitor in resp: - table.add_row([vnf_name,monitor['name'],monitor['value-integer'],monitor['units']]) - table.align='l' - print(table) - -@cli.command(name='ns-monitoring-show') -@click.argument('ns_name') -@click.pass_context -def ns_monitoring_show(ctx,ns_name): - resp=ctx.obj.get_ns_monitoring(ns_name) - table=PrettyTable(['vnf name','monitoring name','value','units']) - if resp is not None: - for key,val in resp.items(): - for monitor in val: - table.add_row([key,monitor['name'],monitor['value-integer'],monitor['units']]) - table.align='l' - print(table) - -@cli.command(name='ns-create') -@click.argument('nsd_name') -@click.argument('ns_name') -@click.argument('vim_account') -@click.option('--admin_status',default='ENABLED',help='administration status') -@click.option('--ssh_keys',default=None,help='comma separated list of keys to inject to vnfs') -@click.option('--vim_network_prefix',default=None,help='vim network name prefix') -@click.pass_context -def ns_create(ctx,nsd_name,ns_name,vim_account,admin_status,ssh_keys,vim_network_prefix): - ctx.obj.instantiate_ns(nsd_name,ns_name,vim_network_prefix=vim_network_prefix,ssh_keys=ssh_keys,account=vim_account) - -@cli.command(name='ns-delete') -@click.argument('ns_name') -@click.pass_context -def ns_delete(ctx,ns_name): - ctx.obj.terminate_ns(ns_name) - -''' -@cli.command(name='keypair-list') -@click.pass_context -def keypair_list(ctx): - resp=ctx.obj.list_key_pair() - table=PrettyTable(['key Name','key']) - for kp in resp: - table.add_row([kp['name'],kp['key']]) - table.align='l' - print(table) -''' - -@cli.command(name='upload-package') -@click.argument('filename') -@click.pass_context -def upload_package(ctx,filename): - ctx.obj.upload_package(filename) - -@cli.command(name='ns-show') -@click.argument('ns_name') -@click.pass_context -def ns_show(ctx,ns_name): - ctx.obj.show_ns(ns_name) - -@cli.command(name='ns-scaling-show') -@click.argument('ns_name') -@click.pass_context -def show_ns_scaling(ctx,ns_name): - ctx.obj.show_ns_scaling(ns_name) - -@cli.command(name='nsd-delete') -@click.argument('nsd_name') -@click.pass_context -def nsd_delete(ctx,nsd_name): - ctx.obj.delete_nsd(nsd_name) - -@cli.command(name='vnfd-delete') -@click.argument('vnfd_name') -@click.pass_context -def nsd_delete(ctx,vnfd_name): - ctx.obj.delete_vnfd(vnfd_name) - -@cli.command(name='config-agent-list') -@click.pass_context -def config_agent_list(ctx): - table=PrettyTable(['name','account-type','details']) - for account in ctx.obj.get_config_agents(): - table.add_row([account['name'],account['account-type'],account['juju']]) - table.align='l' - print(table) - -@cli.command(name='config-agent-delete') -@click.argument('name') -@click.pass_context -def config_agent_delete(ctx,name): - ctx.obj.delete_config_agent(name) - -@cli.command(name='config-agent-add') -@click.argument('name') -@click.argument('account_type') -@click.argument('server') -@click.argument('user') -@click.argument('secret') -@click.pass_context -def config_agent_add(ctx,name,account_type,server,user,secret): - ctx.obj.add_config_agent(name,account_type,server,user,secret) - -''' -@cli.command(name='vim-create') -@click.argument('name') -@click.argument('user') -@click.argument('password') -@click.argument('auth_url') -@click.argument('tenant') -@click.argument('mgmt_network') -@click.argument('floating_ip_pool') -@click.option('--account_type',default='openstack') -@click.pass_context -def vim_create(ctx,name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type): - ctx.obj.add_vim_account(name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type) -''' - -@cli.command(name='vim-list') -@click.pass_context -def vim_list(ctx): - resp=ctx.obj.list_vim_accounts() - table=PrettyTable(['ro-account','datacenter name','uuid']) - for roaccount in resp: - for datacenter in roaccount['datacenters']: - table.add_row([roaccount['name'],datacenter['name'],datacenter['uuid']]) - table.align='l' - print(table) - -@cli.command(name='vcs-list') -@click.pass_context -def vcs_list(ctx): - resp=ctx.obj.get_vcs_info() - table=PrettyTable(['component name','state']) - for component in resp: - table.add_row([component['component_name'],component['state']]) - table.align='l' - print(table) - -if __name__ == '__main__': - cli() diff --git a/python-osmclient/setup.py b/python-osmclient/setup.py deleted file mode 100644 index 13b1771a1..000000000 --- a/python-osmclient/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup,find_packages - -setup( - name='osmclient', - version='0.1', - author='Mike Marchetti', - author_email='mmarchetti@sandvine.com', - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'Click','prettytable' - ], - entry_points=''' - [console_scripts] - osm=osmclient.scripts.osm:cli - ''', -) diff --git a/python-osmclient/stdeb.cfg b/python-osmclient/stdeb.cfg deleted file mode 100644 index eadf0faf8..000000000 --- a/python-osmclient/stdeb.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[DEFAULT] -Depends: python-setuptools, python-pycurl, python-click, python-prettytable diff --git a/skyquake/.storybook/config.js b/skyquake/.storybook/config.js index 42edf3cd4..d7168a91d 100644 --- a/skyquake/.storybook/config.js +++ b/skyquake/.storybook/config.js @@ -6,8 +6,12 @@ function loadStories() { // require('../tests/stories/sshKeyCard'); // require('../tests/stories/button'); // require('../tests/stories/sq-input-slider'); - require('../tests/stories/catalogCard'); + // require('../tests/stories/catalogCard'); + require('../tests/stories/FileManager'); + require('../tests/stories/inputs'); // require as many stories as you need. } configure(loadStories, module); + + diff --git a/skyquake/framework/core/api_utils/auth.js b/skyquake/framework/core/api_utils/auth.js new file mode 100644 index 000000000..7249ac71e --- /dev/null +++ b/skyquake/framework/core/api_utils/auth.js @@ -0,0 +1,145 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Auth util for use across the api_server. + * @module framework/core/api_utils/auth + * @author Kiran Kashalkar + */ + +var jsonLoader = require('require-json'); +var passport = require('passport'); +var OpenIdConnectStrategy = require('passport-openidconnect').Strategy; +var BearerStrategy = require('passport-http-bearer').Strategy; +var OAuth2Strategy = require('passport-oauth2'); +var OAuth2RefreshTokenStrategy = require('passport-oauth2-middleware').Strategy; +var openidConnectConfig = require('./openidconnect_config.json'); +var _ = require('lodash'); +var constants = require('./constants'); +var utils = require('./utils'); +var request = utils.request; +var rp = require('request-promise'); +var nodeutil = require('util'); + + +var Authorization = function(openidConfig) { + + var self = this; + + self.passport = passport; + + self.openidConnectConfig = openidConnectConfig; + + var refreshStrategy = new OAuth2RefreshTokenStrategy({ + refreshWindow: constants.REFRESH_WINDOW, // Time in seconds to perform a token refresh before it expires + userProperty: 'user', // Active user property name to store OAuth tokens + authenticationURL: '/login', // URL to redirect unauthorized users to + callbackParameter: 'callback' //URL query parameter name to pass a return URL + }); + + self.passport.use('main', refreshStrategy); + + var openidConfigPrefix = openidConfig.idpServerProtocol + '://' + openidConfig.idpServerAddress + ':' + openidConfig.idpServerPortNumber; + + self.openidConnectConfig.authorizationURL = openidConfigPrefix + self.openidConnectConfig.authorizationURL; + self.openidConnectConfig.tokenURL = openidConfigPrefix + self.openidConnectConfig.tokenURL; + self.openidConnectConfig.callbackURL = openidConfig.callbackServerProtocol + '://' + openidConfig.callbackAddress + ':' + openidConfig.callbackPortNumber + self.openidConnectConfig.callbackURL; + + var userInfoURL = openidConfigPrefix + self.openidConnectConfig.userInfoURL; + + function SkyquakeOAuth2Strategy(options, verify) { + OAuth2Strategy.call(this, options, verify); + } + nodeutil.inherits(SkyquakeOAuth2Strategy, OAuth2Strategy); + + SkyquakeOAuth2Strategy.prototype.userProfile = function(access_token, done) { + + var requestHeaders = { + 'Authorization': 'Bearer ' + access_token + }; + + request({ + url: userInfoURL, + type: 'GET', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: constants.REJECT_UNAUTHORIZED + }, function(err, response, body) { + if (err) { + console.log('Error obtaining userinfo: ', err); + return done(null, { + username: '', + subject: '' + }); + } else { + if (response.statusCode == constants.HTTP_RESPONSE_CODES.SUCCESS.OK) { + try { + var data = JSON.parse(response.body); + var username = data['preferred_username']; + var subject = data['sub']; + var domain = data['user_domain'] || 'system'; + return done(null, { + username: username, + subject: subject, + domain: domain + }) + } catch (ex) { + console.log('Error parsing userinfo data'); + return done(null, { + username: '', + subject: '' + }); + } + } + } + }) + }; + + var oauthStrategy = new SkyquakeOAuth2Strategy(self.openidConnectConfig, + refreshStrategy.getOAuth2StrategyCallback()); + + self.passport.use('oauth2', oauthStrategy); + refreshStrategy.useOAuth2Strategy(oauthStrategy); + + self.passport.serializeUser(function(user, done) { + done(null, user); + }); + + self.passport.deserializeUser(function(obj, done) { + done(null, obj); + }); + +}; + +Authorization.prototype.configure = function(config) { + this.config = config; + // Initialize Passport and restore authentication state, if any, from the + // session. + if (this.config.app) { + this.config.app.use(this.passport.initialize()); + this.config.app.use(this.passport.session()); + } else { + console.log('FATAL error. Bad config passed into authorization module'); + } +}; + +Authorization.prototype.invalidate_token = function(token) { + +}; + +module.exports = Authorization; diff --git a/skyquake/framework/core/api_utils/constants.js b/skyquake/framework/core/api_utils/constants.js index 0aac7d2d0..b27e7da30 100644 --- a/skyquake/framework/core/api_utils/constants.js +++ b/skyquake/framework/core/api_utils/constants.js @@ -73,11 +73,21 @@ constants.SOCKET_BASE_PORT = 3500; constants.SOCKET_POOL_LENGTH = 20; constants.SERVER_PORT = process.env.SERVER_PORT || 8000; constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443; +constants.REJECT_UNAUTHORIZED = false; -constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload/packages/'; +constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload'; constants.PACKAGE_MANAGER_SERVER_PORT = 4567; constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS = 3 * 1000 * 60; //5 minutes constants.PACKAGE_FILE_ONBOARD_TRANSACTION_STATUS_CHECK_DELAY_MILLISECONDS = 2 * 1000; //2 seconds +constants.REFRESH_WINDOW = 10; //Time in seconds to perform a token refresh before it expires +constants.LAUNCHPAD_ADDRESS = 'localhost'; +constants.LAUNCHPAD_PORT = 8008; +constants.IDP_SERVER_PROTOCOL = 'https'; +constants.IDP_PORT_NUMBER = 8009; +constants.CALLBACK_SERVER_PROTOCOL = 'https'; +constants.CALLBACK_PORT_NUMBER = 8443; +constants.CALLBACK_ADDRESS = 'localhost'; +constants.END_SESSION_PATH = 'end_session'; module.exports = constants; \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/csrf.js b/skyquake/framework/core/api_utils/csrf.js new file mode 100644 index 000000000..62855f27a --- /dev/null +++ b/skyquake/framework/core/api_utils/csrf.js @@ -0,0 +1,67 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * CSRF util for use across the api_server. + * @module framework/core/api_utils/csrf + * @author Kiran Kashalkar + */ + +var constants = require('./constants.js'); +var utils = require('./utils.js'); + +var target = null; + +function configure(config) { + target = config.target; +} + +function csrfCheck(req, res, next) { + var host = null; + + if (req.headers.origin != 'null') { + host = utils.getHostNameFromURL(req.headers.origin); + } else if (req.headers.referer) { + host = utils.getHostNameFromURL(req.headers.referer); + } else { + var msg = 'Request did not contain an origin or referer header. Request terminated.'; + var error = {}; + error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED; + error.errorMessage = { + error: msg + } + return utils.sendErrorResponse(error, res); + } + + if (!host || host != target) { + var msg = 'Request did not originate from authorized source (Potential CSRF attempt). Request terminated.'; + var error = {}; + error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED; + error.errorMessage = { + error: msg + } + return utils.sendErrorResponse(error, res); + } else { + return next(); + } +} + +module.exports = { + configure: configure, + csrfCheck: csrfCheck +}; \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/openidconnect_config.json b/skyquake/framework/core/api_utils/openidconnect_config.json new file mode 100644 index 000000000..63c4a1d31 --- /dev/null +++ b/skyquake/framework/core/api_utils/openidconnect_config.json @@ -0,0 +1,9 @@ +{ + "scope": "openid", + "clientID": "cncudWkub3BlbmlkY2xpZW50", + "clientSecret": "riftiorocks", + "authorizationURL": "/authorization", + "tokenURL": "/token", + "userInfoURL": "/userinfo", + "callbackURL": "/callback" +} \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/sockets.js b/skyquake/framework/core/api_utils/sockets.js index 5e0b25bfb..a86f5ed35 100644 --- a/skyquake/framework/core/api_utils/sockets.js +++ b/skyquake/framework/core/api_utils/sockets.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ var url = require('url'); var sockjs = require('sockjs'); var websocket_multiplex = require('websocket-multiplex'); var utils = require('./utils.js'); +var configurationAPI = require('../modules/api/configuration.js'); var Subscriptions = function() { @@ -182,7 +183,7 @@ Subscriptions.prototype.socketInstance = function(url, req, wss, Type, channelId if (Type == PollingSocket) { Socket = new Type(url, req, 1000, req.body); } else { - Socket = new Type(url); + Socket = new Type(url, ['Bearer', req.session.passport.user.user['access_token']]); } console.log('Socket assigned for url', url); } @@ -278,12 +279,12 @@ function PollingSocket(url, req, interval, config) { self.isClosed = false; var requestHeaders = {}; _.extend(requestHeaders, { - 'Authorization': req.get('Authorization') + Cookie: req.get('Cookie') }); var pollServer = function() { Request({ - url: url, + url: utils.projectContextUrl(req, url), method: config.method || 'GET', headers: requestHeaders, json: config.payload, @@ -294,7 +295,11 @@ function PollingSocket(url, req, interval, config) { console.log('Error polling: ' + url); } else { if (!self.isClosed) { - self.poll = setTimeout(pollServer, 1000 || interval); + if(process.env.DISABLE_POLLING != "TRUE") { + self.poll = setTimeout(pollServer, 1000 || interval); + } else { + console.log('Polling is disabled. Finishing request.') + } var data = response.body; if (self.onmessage) { self.onmessage(data); diff --git a/skyquake/framework/core/api_utils/utils.js b/skyquake/framework/core/api_utils/utils.js index 5b17279d5..cdf12fc91 100644 --- a/skyquake/framework/core/api_utils/utils.js +++ b/skyquake/framework/core/api_utils/utils.js @@ -49,6 +49,41 @@ var confdPort = function(api_server) { return api_server + ':' + CONFD_PORT; }; +var projectContextUrl = function(req, url) { + //NOTE: We need to go into the sessionStore because express-session + // does not reliably update the session. + // See https://github.com/expressjs/session/issues/450 + var projectId = (req.session && + req.sessionStore && + req.sessionStore.sessions && + req.sessionStore.sessions[req.session.id] && + JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) || + (null); + if (projectId) { + projectId = encodeURIComponent(projectId); + return url.replace(/(\/api\/operational\/|\/api\/config\/)(.*)/, '$1project/' + projectId + '/$2'); + } + return url; +} + +var addProjectContextToRPCPayload = function(req, url, inputPayload) { + //NOTE: We need to go into the sessionStore because express-session + // does not reliably update the session. + // See https://github.com/expressjs/session/issues/450 + var projectId = (req.session && + req.sessionStore && + req.sessionStore.sessions && + req.sessionStore.sessions[req.session.id] && + JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) || + (null); + if (projectId) { + if (url.indexOf('/api/operations/')) { + inputPayload['project-name'] = projectId; + } + } + return inputPayload; +} + var validateResponse = function(callerName, error, response, body, resolve, reject) { var res = {}; @@ -61,12 +96,12 @@ var validateResponse = function(callerName, error, response, body, resolve, reje }; reject(res); return false; - } else if (response.statusCode >= 400) { + } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) { console.log('Problem with "', callerName, '": ', response.statusCode, ':', body); res.statusCode = response.statusCode; // auth specific - if (response.statusCode == 401) { + if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) { res.errorMessage = { error: 'Authentication needed' + body }; @@ -81,7 +116,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje reject(res); return false; - } else if (response.statusCode == 204) { + } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) { resolve({ statusCode: response.statusCode, data: {} @@ -95,7 +130,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje var checkAuthorizationHeader = function(req) { return new Promise(function(resolve, reject) { - if (req.get('Authorization') == null) { + if (req.session && req.session.authorization == null) { reject(); } else { resolve(); @@ -119,12 +154,12 @@ if (process.env.LOG_REQUESTS) { reject(res); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error: ' + error); return false; - } else if (response.statusCode >= 400) { + } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) { console.log('Problem with "', callerName, '": ', response.statusCode, ':', body); res.statusCode = response.statusCode; // auth specific - if (response.statusCode == 401) { + if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) { res.errorMessage = { error: 'Authentication needed' + body }; @@ -140,7 +175,7 @@ if (process.env.LOG_REQUESTS) { reject(res); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body); return false; - } else if (response.statusCode == 204) { + } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) { resolve(); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body); return false; @@ -162,6 +197,9 @@ if (process.env.LOG_REQUESTS) { * @param {Function} res - a handle to the express response function */ var sendErrorResponse = function(error, res) { + if (!error.statusCode) { + console.error('Status Code has not been set in error object: ', error); + } res.status(error.statusCode); res.send(error); } @@ -197,10 +235,10 @@ var passThroughConstructor = function(app) { } new Promise(function(resolve, reject) { request({ - uri: uri, + uri: projectContextUrl(req, uri), method: 'GET', headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], { - 'Authorization': req.get('Authorization'), + 'Authorization': req.session && req.session.authorization, forever: CONSTANTS.FOREVER_ON, rejectUnauthorized: false, }) @@ -226,6 +264,31 @@ var getPortForProtocol = function(protocol) { } } +var buildRedirectURL = function(req, globalConfiguration, plugin, extra) { + var api_server = req.query['api_server'] || (req.protocol + '://' + globalConfiguration.get().api_server); + var download_server = req.query['dev_download_server'] || globalConfiguration.get().dev_download_server; + var url = '/'; + url += plugin; + url += '/?api_server=' + api_server; + url += download_server ? '&dev_download_server=' + download_server : ''; + url += extra || ''; + return url; +} + +var getHostNameFromURL = function(url) { + var match = url.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([^?#]*)(\?[^#]*|)(#.*|)$/); + return match && match[3]; +} + +var dataToJsonSansPropNameNamespace = function(s) { + var a = JSON.parse(s); + var b = JSON.stringify(a); + var c = b.replace(/{"[-\w]+:/g, '{"'); + var d = c.replace(/,"[-\w]+:/g, ',"'); + var j = JSON.parse(d); + return j; +} + module.exports = { /** * Ensure confd port is on api_server variable. @@ -244,5 +307,15 @@ module.exports = { passThroughConstructor: passThroughConstructor, - getPortForProtocol: getPortForProtocol + getPortForProtocol: getPortForProtocol, + + projectContextUrl: projectContextUrl, + + addProjectContextToRPCPayload: addProjectContextToRPCPayload, + + buildRedirectURL: buildRedirectURL, + + getHostNameFromURL: getHostNameFromURL, + + dataToJsonSansPropNameNamespace: dataToJsonSansPropNameNamespace }; diff --git a/skyquake/framework/core/modules/api/appConfigAPI.js b/skyquake/framework/core/modules/api/appConfigAPI.js new file mode 100644 index 000000000..ce93bdc4c --- /dev/null +++ b/skyquake/framework/core/modules/api/appConfigAPI.js @@ -0,0 +1,119 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var request = require('request'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var sessionAPI = require('./sessions.js'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/app-config', cors(), function (req, res) { + getConfig(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +var inactivityTimeout = process.env.UI_TIMEOUT_SECS || 600000; + +var versionPromise = null; + +var init = function () { + versionPromise = new Promise( + function (resolve, reject) { + sessionAPI.sessionPromise.then( + function (session) { + request({ + url: configuration.getBackendURL() + '/api/operational/version', + type: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false + }, + function (error, response, body) { + var data; + if (utils.validateResponse('schema/version.get', error, response, body, resolve, reject)) { + try { + data = JSON.parse(response.body)['rw-base:version']; + resolve(data.version); + } catch (e) { + return reject({}); + } + } else { + console.log(error); + } + }); + }); + }); +} + +var getConfig = function (req) { + var api_server = req.query['api_server']; + + var requests = [versionPromise]; + + return new Promise(function (resolve, reject) { + Promise.all(requests).then( + function (results) { + var data = { + version: results[0], + 'api-server': configuration.getBackendURL, + 'inactivity-timeout': process.env.UI_TIMEOUT_SECS || 600000 + } + resolve({ + data: data, + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK + }); + }).catch( + function (error) { + var response = {}; + console.log('Problem with config.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get config' + error + }; + reject(response); + }); + }); +}; + +module.exports = { + getRouter: function () { + return router; + }, + init: init +}; \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/configuration.js b/skyquake/framework/core/modules/api/configuration.js index 376264396..d1fca27ee 100644 --- a/skyquake/framework/core/modules/api/configuration.js +++ b/skyquake/framework/core/modules/api/configuration.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,11 @@ var configurationAPI = {}; var _ = require('lodash'); var GLOBAL_CONFIGURATION = { api_server: 'localhost', - ssl_enabled: true + ssl_enabled: true, + api_server_port_number: constants.SECURE_SERVER_PORT, + idp_server_address: constants.LAUNCHPAD_ADDRESS, + idp_server_protocol: constants.IDP_SERVER_PROTOCOL, + idp_server_port_number: constants.IDP_PORT_NUMBER }; /** @@ -92,4 +96,18 @@ configurationAPI.globalConfiguration.get = function() { return GLOBAL_CONFIGURATION; }; +var backendURL = null; +configurationAPI.getBackendURL = function () { + if (!backendURL) { + backendURL = GLOBAL_CONFIGURATION.api_server_protocol + '://' + GLOBAL_CONFIGURATION.api_server + ':' + GLOBAL_CONFIGURATION.api_server_port_number; + } + return backendURL; +} + +configurationAPI.getBackendAPI = function () { + return configurationAPI.getBackendURL() + '/v2/api'; +} + + + module.exports = configurationAPI; diff --git a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js index b0223b2ab..34d30b36e 100644 --- a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js +++ b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js @@ -36,7 +36,7 @@ ModelMeta.get = function(req) { uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, @@ -46,7 +46,7 @@ ModelMeta.get = function(req) { uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, diff --git a/skyquake/framework/core/modules/api/modelAPI.js b/skyquake/framework/core/modules/api/modelAPI.js new file mode 100644 index 000000000..1f14cb798 --- /dev/null +++ b/skyquake/framework/core/modules/api/modelAPI.js @@ -0,0 +1,359 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/model', cors(), function (req, res) { + get(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.patch('/model', cors(), function (req, res) { + update(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.put('/model', cors(), function (req, res) { + add(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.delete('/model', cors(), function (req, res) { + remove(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = { + getRouter: function () { + return router; + }, + init: function () {} +}; + +get = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var requestHeaders = _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.session && req.session.authorization + }) + return new Promise(function (resolve, reject) { + Promise.all([ + rp({ + uri: backend + '/config' + modelPath, + method: 'GET', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + // }), + // rp({ + // uri: utils.projectContextUrl(req, backend + '/api/operational' + modelPath), + // method: 'GET', + // headers: requestHeaders, + // forever: constants.FOREVER_ON, + // rejectUnauthorized: false, + // resolveWithFullResponse: true + }) + ]).then(function (results) { + var response = { + statusCode: results[0].statusCode || 200, + data: [null] + } + if (results[0].body && !results[0].error) { + var result = JSON.parse(results[0].body); + if (result.collection) { + result = result.collection[Object.keys(result.collection)[0]]; + if (!result.length) { + result = null; + } else if (result.length === 1) { + result = result[0]; + } + } + response.data = result; + } + resolve(response); + + }).catch(function (error) { + var res = {}; + console.log('Problem with model get', error); + res.statusCode = error.statusCode || 500; + res.errorMessage = { + error: 'Failed to get model: ' + error + }; + reject(res); + }); + }); +}; + +add = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var targetProperty = modelPath.split('/').pop(); + var newElement = {}; + var data = {}; + console.log(req.body); + _.forIn(req.body, function (value, key) { + if (_.isObject(value)) { + if (value.type === 'leaf_empty') { + if (value.data) { + data[key] = ' '; + } + } else if (value.type === 'leaf_list') { + data[key] = value.data.add; + } + } else { + data[key] = value; + } + }); + newElement[targetProperty] = [data]; + console.log(newElement); + var target = backend + '/config' + modelPath; + var method = 'POST' + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }); + return new Promise(function (resolve, reject) { + rp({ + uri: target, + method: method, + headers: requestHeaders, + forever: constants.FOREVER_ON, + json: newElement, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function (results) { + var response = {}; + response.data = { + path: modelPath, + data: data + }; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + console.log(response); + resolve(response); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }); +}; + +update = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }); + var base = backend + '/config' + modelPath + '/'; + + function getUpdatePromise(name, value) { + var data = {}; + data[name] = value; + return new Promise(function (resolve, reject) { + rp({ + uri: base + name, + method: value ? 'PATCH' : 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function (result) { + resolve({ + element: name, + success: true, + value: value + }); + }).catch(function (result) { + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + resolve({ + element: name, + success: false, + error: error, + value: value + }); + }) + }) + } + + function getDeletePromise(targetProp, item) { + if (item) { + targetProp = targetProp + '/' + item; + } + return getUpdatePromise(targetProp, ''); + } + + var updates = []; + _.forIn(req.body, function (value, key) { + var data = {}; + if (_.isObject(value)) { + if (value.type === 'leaf_list') { + _.forEach(value.data.remove, function (v) { + updates.push(getDeletePromise(key)) + }) + _.forEach(value.data.add, function (v) { + updates.push(getUpdatePromise(key, v)) + }) + } else if (value.type === 'leaf_empty') { + if (value.data) { + updates.push(getUpdatePromise(key, ' ')) + } else { + updates.push(getDeletePromise(key)) + } + } + } else { + updates.push(getUpdatePromise(key, value)) + } + }) + + return new Promise(function (resolve, reject) { + Promise.all(updates).then(function (results) { + var response = {}; + var output = {}; + var hasError = false; + _.forEach(results, function (result) { + var record = {}; + if (output[result.element]) { + if (_.isArray(output[result.element].value)) { + output[result.element].value.push(result.value); + } else { + output[result.element].value = [output[result.element].value, result.value]; + } + } else { + output[result.element] = result; + } + hasError = hasError || !result.success + }) + response.data = { + result: output, + hasError: hasError + }; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + console.log(response); + resolve(response); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }); +}; + +remove = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var target = backend + '/config' + modelPath; + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }) + return new Promise(function (resolve, reject) { + rp({ + url: target, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }).then(function (response) { + return resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: modelPath + }); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }) +} \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/projectManagementAPI.js b/skyquake/framework/core/modules/api/projectManagementAPI.js new file mode 100644 index 000000000..d65e8905a --- /dev/null +++ b/skyquake/framework/core/modules/api/projectManagementAPI.js @@ -0,0 +1,297 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var ProjectManagement = {}; +var Promise = require('bluebird'); +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var API_VERSION = 'v2'; +ProjectManagement.get = function(req, fields) { + var self = this; + var api_server = req.query['api_server']; + // by default just load basic info as this request is expensive + fields = fields || ['name', 'description', 'project-config']; + var select = fields.length ? '?fields=' + fields.join(';') : ''; + + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: `${utils.confdPort(api_server)}/${API_VERSION}/api/operational/project` + select, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data']['project'] = JSON.parse(result[0].body)['rw-project:project']; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get ProjectManagement' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.create = function(req) { + var self = this; + var api_server = req.query['api_server']; + var data = req.body; + data = { + "project":[data] + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.create', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +ProjectManagement.update = function(req) { + //"rw-project:project" + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + // oddly enough, if we do not encode this here letting the request below does so incorrectly + var projectName = encodeURIComponent(bodyData.name); + var descriptionData = { + "rw-project:project" : { + "name": bodyData.name, + "description": bodyData.description + } + } + var updateTasks = []; + var baseUrl = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectName + var updateProjectConfig = rp({ + uri: baseUrl + '/project-config', + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "project-config": bodyData['project-config'] + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateProjectConfig); + + var updateProjectDescription = rp({ + uri: baseUrl + '/description', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: {"description": bodyData.description}, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateProjectDescription) + return new Promise(function(resolve, reject) { + Promise.all( + updateTasks + ).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.update', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to update project - ' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.delete = function(req) { + var self = this; + var projectname = encodeURIComponent(req.params.projectname); + var api_server = req.query["api_server"]; + var requestHeaders = {}; + var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectname + return new Promise(function(resolve, reject) { + _.extend(requestHeaders, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }); + rp({ + url: url, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }, function(error, response, body) { + if (utils.validateResponse('ProjectManagement.DELETE', error, response, body, resolve, reject)) { + return resolve({ + statusCode: response.statusCode, + data: JSON.stringify(response.body) + }); + }; + }); + }) +} + + +ProjectManagement.getPlatform = function(req, userId) { + var self = this; + var api_server = req.query['api_server']; + var user = req.params['userId'] || userId; + return new Promise(function(resolve, reject) { + var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/rbac-platform-config'; + if(user) { + url = url + '/user/' + encodeURIComponent(user); + } + Promise.all([ + rp({ + uri: url, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + if(user) { + response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:user']; + } else { + response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config']; + } + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.getPlatform', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get ProjectManagement.getPlatform' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.updatePlatform = function(req) { + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + data = bodyData; + data.user = JSON.parse(data.user) + var updateTasks = []; + + var updatePlatform = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/rbac-platform-config', + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "rw-rbac-platform:rbac-platform-config": data + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updatePlatform) + return new Promise(function(resolve, reject) { + Promise.all([ + updateTasks + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.updatePlatform', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to update platform - ' + error + }; + reject(response); + }); + }); +}; + + +module.exports = ProjectManagement; diff --git a/skyquake/framework/core/modules/api/restconf.js b/skyquake/framework/core/modules/api/restconf.js index 5ba0eb5aa..03f27213e 100644 --- a/skyquake/framework/core/modules/api/restconf.js +++ b/skyquake/framework/core/modules/api/restconf.js @@ -45,7 +45,7 @@ restconfAPI['streams'].get = function(req) { url: uri + url + '?deep', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, diff --git a/skyquake/framework/core/modules/api/schemaAPI.js b/skyquake/framework/core/modules/api/schemaAPI.js new file mode 100644 index 000000000..34c83be26 --- /dev/null +++ b/skyquake/framework/core/modules/api/schemaAPI.js @@ -0,0 +1,100 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/schema', cors(), function (req, res) { + getSchema(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = { + getRouter: function () { + return router; + }, + init: function () {} +}; + +getSchema = function (req) { + var schemaURI = configuration.getBackendURL() + '/api/schema/'; + var schemaPaths = req.query['request']; + var paths = schemaPaths.split(','); + + function getSchemaRequest(path) { + return rp({ + uri: schemaURI + path, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + } + + var requests = _.map(paths, getSchemaRequest); + + return new Promise(function (resolve, reject) { + Promise.all(requests).then( + function (results) { + var data = { + schema: {} + } + _.forEach(results, function (result, index) { + data.schema[paths[index]] = JSON.parse(result.body); + }); + resolve({ + data: data, + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK + }); + }).catch( + function (error) { + var response = {}; + console.log('Problem with schema.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get schema' + error + }; + reject(response); + }); + }); +}; \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/sessions.js b/skyquake/framework/core/modules/api/sessions.js new file mode 100644 index 000000000..f16872454 --- /dev/null +++ b/skyquake/framework/core/modules/api/sessions.js @@ -0,0 +1,294 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * sessions api module. Provides API functions for sessions + * @module framework/core/modules/api/sessions + * @author Kiran Kashalkar + */ +"use strict" +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var request = utils.request; +var rp = require('request-promise'); +var sessionsAPI = {}; +var _ = require('lodash'); +var base64 = require('base-64'); +var APIVersion = '/v2'; +var configurationAPI = require('./configuration'); +var UserManagement = require('./userManagementAPI.js'); +var URL = require('url'); + +// Used for determining what page a user should first go to. +var Application = { + order: [ + "rw-rbac-platform:super-admin", + "rw-rbac-platform:platform-admin", + "rw-rbac-platform:platform-oper", + "rw-project:project-admin", + "rw-project:project-oper", + "rw-project-mano:lcm-admin", + "rw-project-mano:lcm-oper", + "rw-project-mano:catalog-admin", + "rw-project-mano:catalog-oper", + "rw-project-mano:account-admin", + "rw-project-mano:account-oper" + ], + key: { + "rw-rbac-platform:super-admin": "user_management", + "rw-rbac-platform:platform-admin": "user_management", + "rw-rbac-platform:platform-oper": "user_management", + "rw-project:project-admin": "project_management", + "rw-project:project-oper": "project_management", + "rw-project-mano:catalog-admin": "composer", + "rw-project-mano:catalog-oper": "composer", + "rw-project-mano:lcm-admin": "launchpad", + "rw-project-mano:lcm-oper": "launchpad", + "rw-project-mano:account-admin": "accounts", + "rw-project-mano:account-oper": "accounts" + } +}; + +function logAndReject(mesg, reject, errCode) { + var res = {}; + res.errorMessage = { + error: mesg + } + res.statusCode = errCode || constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST; + console.log(mesg); + reject(res); +} + +function logAndRedirectToLogin(mesg, res, req, invalid) { + console.log(mesg); + if (!invalid) { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer))); + } + res.end(); +} + +function logAndRedirectToEndSession(mesg, res, authorization, url) { + console.log(mesg); + res.set({ + 'Authorization': authorization + }); + res.redirect(url); + res.end(); +} +var sessionPromiseResolve = null; +sessionsAPI.sessionPromise = new Promise(function(resolve, reject) { + sessionPromiseResolve = resolve; +}); + +sessionsAPI.create = function (req, res) { + if (!req.session.passport){ + logAndRedirectToLogin("lost session", res, req); + return new Promise(function (resolve, reject){reject("lost session")}); + } + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + var uri = utils.confdPort(api_server); + var username = req.session.passport.user['username']; + var authorization_header_string = 'Bearer ' + req.session.passport.user.user.access_token; + return new Promise(function (resolve, reject) { + req.session.authorization = authorization_header_string; + req.session.api_server = api_server; + req.session.api_protocal = req.protocol; + req.session.loggedIn = true; + req.session.userdata = { + username: username, + }; + UserManagement.getUserInfo(req, req.session.passport.user.username).then(function (results) { + var project_list_for_user = null; + if (!req.session.projectId && results.data.project) { + project_list_for_user = Object.keys(results.data.project); + if (project_list_for_user.length > 0) { + req.session.projectId = project_list_for_user.sort() && project_list_for_user[0]; + } + } + sessionsAPI.setTopApplication(req); + req.session.isLCM = results.data.isLCM; + + req.session['ui-state'] = results.data['ui-state']; + var lastActiveProject = req.session['ui-state'] && req.session['ui-state']['last-active-project']; + if (lastActiveProject) { + if (results.data.project.hasOwnProperty(lastActiveProject)) { + req.session.projectId = lastActiveProject; + } + + } + + var successMsg = 'User => ' + username + ' successfully logged in.'; + successMsg += req.session.projectId ? 'Project => ' + req.session.projectId + ' set as default.' : ''; + + console.log(successMsg); + + req.session.save(function (err) { + if (err) { + console.log('Error saving session to store', err); + } + // no response data, just redirect now that session data is set + if (req.session['ui-state'] && req.session['ui-state']['last-active-uri']) { + var url = URL.parse(req.session['ui-state']['last-active-uri']); + var host = req.headers.host; + var path = url.path; + var hash = url.hash; + var protocol = url.protocol; + var newUrl = protocol + '//' + host + path + (hash?hash:''); + console.log('Redirecting to: ' + newUrl) + res.redirect(newUrl) + } else { + if(req.session.topApplication) { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication)); + } else { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile')); + } + } + }) + + sessionPromiseResolve(req.session); + + }).catch(function (error) { + // Something went wrong - Redirect to /login + var errorMsg = 'Error logging in or getting list of projects. Error: ' + error; + console.log(errorMsg); + logAndRedirectToLogin(errorMsg, res, req); + }); + }) +}; + +sessionsAPI.addProjectToSession = function (req, res) { + return new Promise(function (resolve, reject) { + if (req.session && req.session.loggedIn == true) { + Promise.all([UserManagement.getProfile(req), UserManagement.updateActiveProject(req)]).then(function () { + req.session.projectId = req.params.projectId; + req.session.topApplication = null; + sessionsAPI.setTopApplication(req, req.query.app); + req.session.save(function (err) { + if (err) { + console.log('Error saving session to store', err); + var errorMsg = 'Session does not exist or not logged in'; + logAndReject(errorMsg, reject, constants.HTTP_RESPONSE_CODES.ERROR.NOT_FOUND); + } else { + var successMsg = 'Added project ' + req.session.projectId + ' to session ' + req.sessionID; + console.log(successMsg); + var response = { + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: JSON.stringify({ + status: successMsg + }) + } + return resolve(response); + } + // res.redirect('/'); + }); + + }) + + } + }); +} + +sessionsAPI.delete = function (req, res) { + var idpServerAddress = configurationAPI.globalConfiguration.get().idp_server_address; + var idpServerProtocol = configurationAPI.globalConfiguration.get().idp_server_protocol; + var idpServerPortNumber = configurationAPI.globalConfiguration.get().idp_server_port_number; + var idpEndSessionPath = constants.END_SESSION_PATH; + var url = idpServerProtocol + '://' + + idpServerAddress + ':' + + idpServerPortNumber + '/' + + idpEndSessionPath; + var authorization = req.session.authorization; + return new Promise(function (resolve, reject) { + Promise.all([ + UserManagement.updateActiveUri(req), + new Promise(function (success, failure) { + req.session.destroy(function (err) { + if (err) { + var errorMsg = 'Error deleting session. Error: ' + err; + console.log(errorMsg); + success({ + status: 'error', + message: errorMsg + }); + } + + var successMsg = 'Success deleting session'; + console.log(successMsg); + + success({ + status: 'success', + message: successMsg + }); + }); + }) + ]).then(function (result) { + // assume the session was deleted! + var message = 'Session was deleted. Redirecting to end_session'; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: { + url: url, + message: message + } + }); + + }).catch(function (error) { + var message = "An error occured while deleting session"; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: { + url: url, + message: message + } + }); + }); + }); +} + +sessionsAPI.setTopApplication = function (req, suggestedPlugin) { + var selectedProject = req.session.projectId; + var userProject = selectedProject ? req.session.projectMap[selectedProject] : null; + if (userProject) { + if (suggestedPlugin) { + if (req.session.platformMap['rw-rbac-platform:super-admin']) { + topApplication = suggestedPlugin; + } else { + var roles = _.reduce(Object.keys(Application.key), function (accumulator, role) { + if (Application.key[role] === suggestedPlugin) { + accumulator.push(role); + } + return accumulator; + }, []); + if (_.some(roles, function (role){return userProject.role[role]})) { + req.session.topApplication = suggestedPlugin; + return; + } + } + } + _.some(Application.order, function (role) { + if (userProject.role[role] || req.session.platformMap.role[role]) { + req.session.topApplication = Application.key[role]; + return true; + } + return false; + }) + } +} + +module.exports = sessionsAPI; diff --git a/skyquake/framework/core/modules/api/userManagementAPI.js b/skyquake/framework/core/modules/api/userManagementAPI.js new file mode 100644 index 000000000..834df7ea9 --- /dev/null +++ b/skyquake/framework/core/modules/api/userManagementAPI.js @@ -0,0 +1,530 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var UserManagement = {}; +var Promise = require('bluebird'); +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var ProjectManagementAPI = require('./projectManagementAPI.js'); +var API_VERSION = 'v2'; + +UserManagement.get = function(req) { + var self = this; + var api_server = req.query['api_server']; + + return new Promise(function(resolve, reject) { + var userConfig = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-config/user', + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + var userOp = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-state/user', + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + Promise.all([ + userConfig, + userOp + ]).then(function(result) { + var response = {}; + var userConfig = []; + var userOpData = {}; + response['data'] = {}; + if (result[0].body) { + userConfig = JSON.parse(result[0].body)['rw-user:user']; + } + if (result[1].body) { + JSON.parse(result[1].body)['rw-user:user'].map(function(u) { + userOpData[u['user-domain'] + ',' + u['user-name']] = u; + }) + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + response['data']['user'] = userConfig.map(function(u,i) { + var mergedData = _.merge(u, userOpData[u['user-domain'] + ',' + u['user-name']]); + mergedData.projects = { + ids: [], + data: {} + }; + var projects = mergedData.projects; + mergedData.role && mergedData.role.map(function(r) { + if ((r.role != "rw-project:user-self" )&& (r.role != "rw-rbac-platform:user-self")) { + var projectId = r.keys.split(';')[0]; + if (projectId == "") { + projectId = "platform" + } + if (!projects.data[projectId]) { + projects.ids.push(projectId); + projects.data[projectId] = []; + } + projects.data[projectId].push(r.role); + } + }) + return mergedData; + }) + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.get', error); + response.statusCode = error.statusCode || constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR; + response.errorMessage = { + error: 'Failed to get UserManagement' + error + }; + reject(response); + }); + }); +}; + + +UserManagement.getProfile = function(req) { + var self = this; + var api_server = req.query['api_server']; + return new Promise(function(resolve, reject) { + var response = {}; + try { + var userId = req.session.userdata.username + response['data'] = { + userId: userId, + projectId: req.session.projectId, + domain: req.session.passport.user.domain + }; + UserManagement.getUserInfo(req, userId).then(function(result) { + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + response.data.data = result.data + resolve(response); + }, function(error) { + console.log('Error retrieving getUserInfo'); + response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR; + reject(response); + }) + } catch (e) { + var response = {}; + console.log('Problem with UserManagement.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get UserManagement' + error + }; + reject(response); + } + }); +}; +UserManagement.getUserInfo = function(req, userId, domain) { + var self = this; + var api_server = req.query['api_server']; + var id = req.params['userId'] || userId; + var domain = req.params['domainId'] || domain; + var response = {}; + return new Promise(function(resolve, reject) { + if (id) { + var getProjects = ProjectManagementAPI.get(req, ['name', 'project-config']); + var getPlatformUser = ProjectManagementAPI.getPlatform(req, id); + var getUserUiState = UserManagement.getUserUiState(req); + Promise.all([ + getProjects, + getPlatformUser, + getUserUiState + ]).then(function(result) { + var userData = { + platform: { + role: { + + } + }, + //id/key values for each project + projectId:[], + project: { + /** + * [projectId] : { + * data: [project object], + * role: { + * [roleId]: true + * } + * } + */ + } + } + //Build UI state + var uiState = result[2].data && result[2].data['rw-user:user']; + userData['ui-state'] = uiState['ui-state']; + //Build platform roles + var platformRoles = result[1].data.platform && result[1].data.platform.role; + platformRoles && platformRoles.map(function(r) { + userData.platform.role[r.role] = true + }); + //Build project roles + var projects = result[0].data.project; + var userProjects = []; + projects && projects.map(function(p, i) { + userData.project[p.name] = { + data: p, + role: {} + } + userData.projectId.push(p.name); + if (userData.platform.role['rw-rbac-platform:super-admin']) { + userData.project[p.name] = { + data: p, + role: { + "rw-project:project-admin": true, + "rw-project:project-oper": true, + "rw-project-mano:account-admin": true, + "rw-project-mano:account-oper": true, + "rw-project-mano:catalog-admin": true, + "rw-project-mano:catalog-oper": true, + "rw-project-mano:lcm-admin": true, + "rw-project-mano:lcm-oper": true + } + } + } else { + var users = p['project-config'] && p['project-config'].user; + users && users.map(function(u) { + if(u['user-name'] == id) { + u.role && u.role.map(function(r) { + userData.project[p.name].role[r.role] = true; + if (r.role === 'rw-project:project-admin') { + userData.project[p.name].role["rw-project-mano:account-admin"] = true; + userData.project[p.name].role["rw-project-mano:catalog-admin"] = true; + userData.project[p.name].role["rw-project-mano:lcm-admin"] = true; + userData.isLCM = true; + } else if (r.role === 'rw-project:project-oper') { + userData.project[p.name].role["rw-project-mano:account-oper"] = true; + userData.project[p.name].role["rw-project-mano:catalog-oper"] = true; + userData.project[p.name].role["rw-project-mano:lcm-oper"] = true; + userData.isLCM = true; + } + }); + u["rw-project-mano:mano-role"] && u["rw-project-mano:mano-role"] .map(function(r) { + userData.project[p.name].role[r.role] = true; + if (r.role.indexOf('rw-project-mano:lcm') > -1) { + userData.isLCM = true; + } + }); + } + }) + } + }); + response.data = userData; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + + req.session.projectMap = userData.project; + req.session.platformMap = userData.platform; + resolve(response); + }) + } else { + var errorMsg = 'userId not specified in UserManagement.getUserInfo'; + console.error(errorMsg); + response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST; + response.error = errorMsg; + reject(response) + } + + }) +} +UserManagement.create = function(req) { + var self = this; + var api_server = req.query['api_server']; + var data = req.body; + data = { + "user":[data] + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.create', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.update = function(req) { + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + data = { + "rw-user:user": bodyData + } + var updateTasks = []; + if(bodyData.hasOwnProperty('old-password')) { + var changePW = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operations/change-password', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "input": { + 'user-name' : bodyData['user-name'], + 'user-domain' : bodyData['user-domain'], + 'old-password' : bodyData['old-password'], + 'new-password' : bodyData['new-password'], + 'confirm-password' : bodyData['confirm-password'], + } + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(changePW); + }; + var updateUser = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/' + encodeURIComponent(bodyData['user-name']) + ',' + encodeURIComponent(bodyData['user-domain']), + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateUser) + return new Promise(function(resolve, reject) { + Promise.all([ + updateTasks + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.passwordChange', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to passwordChange user' + error + }; + reject(response); + }); + }); +}; + +UserManagement.delete = function(req) { + var self = this; + var username = req.params.username; + var domain = req.params.domain; + var api_server = req.query["api_server"]; + var requestHeaders = {}; + var url = `${utils.confdPort(api_server)}/${API_VERSION}/api/config/user-config/user/${encodeURIComponent(username)},${encodeURIComponent(domain)}` + return new Promise(function(resolve, reject) { + _.extend(requestHeaders, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }); + rp({ + url: url, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }, function(error, response, body) { + if (utils.validateResponse('UserManagement.DELETE', error, response, body, resolve, reject)) { + return resolve({ + statusCode: response.statusCode, + data: JSON.stringify(response.body) + }); + }; + }); + }) +}; +UserManagement.getUserUiState = function(req) { + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/'+encodeURIComponent(user.username) + ',' + encodeURIComponent(user.domain), + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = JSON.parse(result[0].body); + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.getUserUiState', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.updateActiveProject = function(req) { + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + var data = { + "rw-user:user-config": { + "user":{ + "user-name" : user.username, + "user-domain": user.domain, + "ui-state": { + "last-active-project" : req.params.projectId + } + } + } + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.updateActiveProject', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.updateActiveUri = function(req) { + if (!req.session.passport) { + console.debug("passport gone before we got the save the active uri"); + var response = { + statusCode: 500, + errorMessage: { + error: 'Failed to save active uri' + }}; + return Promise.resolve(response); + } + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + var ref = req.headers.referer; + var hash = req.query.hash; + var data = { + "rw-user:user-config": { + "user":{ + "user-name" : user.username, + "user-domain": user.domain, + "ui-state": { + "last-active-uri" : ref + decodeURIComponent(hash) + } + } + } + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.updateActiveProject', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +module.exports = UserManagement; diff --git a/skyquake/framework/core/modules/navigation_manager.js b/skyquake/framework/core/modules/navigation_manager.js index c85eba618..7d2239459 100644 --- a/skyquake/framework/core/modules/navigation_manager.js +++ b/skyquake/framework/core/modules/navigation_manager.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,6 @@ function addNavigation(plugin_name, routes) { if (!NAVIGATION[plugin_name]) { NAVIGATION[plugin_name] = {}; } - if (!NAVIGATION[plugin_name].routes) { NAVIGATION[plugin_name].routes = routes; } else { @@ -69,6 +68,20 @@ function addLabel(plugin_name, label) { NAVIGATION[plugin_name].label = label || 'RW.UI Plugin'; } +function addAllow(plugin_name, allow) { + if (!NAVIGATION[plugin_name]) { + NAVIGATION[plugin_name] = {}; + } + NAVIGATION[plugin_name].allow = allow || '*'; +} + +function addAdminFlag(plugin_name, admin_link) { + if (!NAVIGATION[plugin_name]) { + NAVIGATION[plugin_name] = {}; + } + NAVIGATION[plugin_name].admin_link = admin_link || false; +} + function getNavigation() { return NAVIGATION; } @@ -82,6 +95,8 @@ function onNavigationDiscovered(plugin_name, plugin) { addOrder(plugin_name, plugin.order); addPriority(plugin_name, plugin.priority); addLabel(plugin_name, plugin.name); + addAllow(plugin_name, plugin.allow); + addAdminFlag(plugin_name, plugin.admin_link); } function init() { diff --git a/skyquake/framework/core/modules/routes/auth.js b/skyquake/framework/core/modules/routes/auth.js new file mode 100644 index 000000000..c1df55a42 --- /dev/null +++ b/skyquake/framework/core/modules/routes/auth.js @@ -0,0 +1,99 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * auth routes module. Provides a RESTful API for this + * skyquake instance's auth state. + * @module framework/core/modules/routes/auth + * @author Kiran Kashalkar + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var configurationAPI = require('../api/configuration'); + +var auth = {}; + +auth.routes = function(authManager) { + console.log('Configuring auth routes'); + Router.use(bodyParser.json()); + Router.use(cors()); + Router.use(bodyParser.urlencoded({ + extended: true + })); + + // Define routes. + Router.get('/', function(req, res) { + var default_page = null; + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + if (req.session && req.session.topApplication) { + default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication); + } else { + default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile'); + } + if (!req.user) { + res.redirect('/login'); + } else { + res.redirect(default_page); + } + }); + + Router.get('/login', cors(), function(req, res) { + // res.render('login.html'); + res.redirect('/login/idp'); + }); + + Router.get('/login/idp', + authManager.passport.authenticate('oauth2') + ); + + Router.get('/callback', function(req, res, next) { + authManager.passport.authenticate('oauth2', function(err, user, info) { + if (err) { + // Catch some errors specific to deployments (e.g. IDP unavailable) + if (err.oauthError && err.oauthError.code == 'ENOTFOUND') { + return res.render('idpconnectfail.ejs', { + callback_url: req.url + }); + } + return res.redirect('/login'); + } + if (!user) { + return res.redirect('/login'); + } + req.logIn(user, function(err) { + if (err) { + return next(err); + } + return res.redirect('/session?redirectParams=' + req.url); + }); + })(req, res, next); + }); + + + Router.get('/login.html', cors(), function(req, res) { + res.render('login.html'); + }); +} + +auth.router = Router; + +module.exports = auth; diff --git a/skyquake/framework/core/modules/routes/configuration.js b/skyquake/framework/core/modules/routes/configuration.js index b789ff043..93bb88aa1 100644 --- a/skyquake/framework/core/modules/routes/configuration.js +++ b/skyquake/framework/core/modules/routes/configuration.js @@ -56,38 +56,5 @@ Router.get('/server-configuration', cors(), function(req, res) { }); }); -Router.get('/check-auth', function(req, res) { - console.log('testing auth') - var api_server = req.query["api_server"]; - var uri = utils.confdPort(api_server) + '/api/config/'; - - checkAuth(uri, req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); -}); - -function checkAuth(uri, req){ - return new Promise(function(resolve, reject) { - request({ - uri: uri, - method: 'GET', - headers: _.extend({}, { - 'Authorization': req.get('Authorization'), - forever: CONSTANTS.FOREVER_ON, - rejectUnauthorized: false, - }) - }, function(error, response, body) { - console.log(arguments) - if( response.statusCode == 401) { - reject({statusCode: 401, error: response.body}); - } else { - resolve({statusCode:200, data:response.body}) - } - }); - }); -} - module.exports = Router; diff --git a/skyquake/framework/core/modules/routes/navigation.js b/skyquake/framework/core/modules/routes/navigation.js index 82c7ec580..37e86e41b 100644 --- a/skyquake/framework/core/modules/routes/navigation.js +++ b/skyquake/framework/core/modules/routes/navigation.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ var navAPI = require('../api/navigation'); var Router = require('express').Router(); var utils = require('../../api_utils/utils'); var configurationAPI = require('../api/configuration'); +var csrfCheck = require('../../api_utils/csrf').csrfCheck; Router.use(bodyParser.json()); Router.use(cors()); @@ -37,48 +38,64 @@ Router.use(bodyParser.urlencoded({ extended: true })); -Router.get('/', cors(), function(req, res, next) { - res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname)); +//Should have a way of adding excluded routes to this via plugin registry, instead of hard coding +Router.use(/^(?!.*(login\/idp|session|composer\/upload|composer\/update)).*/, function(req, res, next) { + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + if (req.session && req.session.loggedIn) { + switch (req.method) { + case 'POST': + case 'PUT': + csrfCheck(req, res, next); + break; + default: + next(); + break; + } + } else { + console.log('Redirect to login.html'); + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer))); + } }); + Router.get('/nav', cors(), function(req, res) { - navAPI.get(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.get('/nav/:plugin_id', cors(), function(req, res) { - navAPI.get(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.post('/nav/:plugin_id', cors(), function(req, res) { - navAPI.create(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.create(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.put('/nav/:plugin_id/:route_id', cors(), function(req, res) { - navAPI.update(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.update(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.delete('/nav/:plugin_id/:route_id', cors(), function(req, res) { - navAPI.delete(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.delete(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); diff --git a/skyquake/framework/core/modules/routes/projectManagement.js b/skyquake/framework/core/modules/routes/projectManagement.js new file mode 100644 index 000000000..c106f30e3 --- /dev/null +++ b/skyquake/framework/core/modules/routes/projectManagement.js @@ -0,0 +1,85 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * inactivity routes module. Provides a RESTful API for this + * skyquake instance's inactivity state. + * @module framework/core/modules/routes/inactivity + * @author Laurence Maultsby + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var ProjectManagementAPI = require('../api/projectManagementAPI.js'); + +Router.use(bodyParser.json()); +Router.use(cors()); +Router.use(bodyParser.urlencoded({ + extended: true +})); + +Router.get('/project', cors(), function(req, res) { + ProjectManagementAPI.get(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.post('/project', cors(), function(req, res) { + ProjectManagementAPI.create(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.put('/project', cors(), function(req, res) { + ProjectManagementAPI.update(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.delete('/project/:projectname', cors(), function(req, res) { + ProjectManagementAPI.delete(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +Router.put('/platform', cors(), function(req, res) { + ProjectManagementAPI.updatePlatform(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +Router.get('/platform', cors(), function(req, res) { + ProjectManagementAPI.getPlatform(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +module.exports = Router; + + + diff --git a/skyquake/framework/core/modules/routes/sessions.js b/skyquake/framework/core/modules/routes/sessions.js new file mode 100644 index 000000000..f84ca11b7 --- /dev/null +++ b/skyquake/framework/core/modules/routes/sessions.js @@ -0,0 +1,74 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Node sessions routes module. + * Provides a RESTful API to manage sessions. + * @module framework/core/modules/routes/sessions + * @author Kiran Kashalkar + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var sessionsAPI = require('../api/sessions'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var CONSTANTS = require('../../api_utils/constants.js'); +var request = require('request'); +var _ = require('lodash'); + +var sessions = {}; + +sessions.routes = function(sessionsConfig) { + Router.use(bodyParser.json()); + Router.use(cors()); + Router.use(bodyParser.urlencoded({ + extended: true + })); + + // Overloaded get method to handle OpenIDConnect redirect to establish a session. + Router.get('/session*', cors(), /*sessionsConfig.authManager.passport.authenticate('main', { + noredirect: false + }), */function(req, res) { + req.query['api_server'] = sessionsConfig.api_server_protocol + '://' + sessionsConfig.api_server; + sessionsAPI.create(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }); + }); + + // For project switcher UI + Router.put('/session/:projectId', cors(), function(req, res) { + sessionsAPI.addProjectToSession(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); + }); + + Router.delete('/session', cors(), function(req, res) { + sessionsAPI.delete(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }); + }); +} + +sessions.router = Router; + + +module.exports = sessions; diff --git a/skyquake/framework/core/modules/routes/userManagement.js b/skyquake/framework/core/modules/routes/userManagement.js new file mode 100644 index 000000000..22a2d74e0 --- /dev/null +++ b/skyquake/framework/core/modules/routes/userManagement.js @@ -0,0 +1,85 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * inactivity routes module. Provides a RESTful API for this + * skyquake instance's inactivity state. + * @module framework/core/modules/routes/inactivity + * @author Laurence Maultsby + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var UserManagementAPI = require('../api/userManagementAPI.js'); + +Router.use(bodyParser.json()); +Router.use(cors()); +Router.use(bodyParser.urlencoded({ + extended: true +})); + +Router.get('/user', cors(), function(req, res) { + UserManagementAPI.get(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.get('/user-profile', cors(), function(req, res) { + UserManagementAPI.getProfile(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.get('/user-data/:userId/:domain?', cors(), function(req, res) { + UserManagementAPI.getUserInfo(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.post('/user', cors(), function(req, res) { + UserManagementAPI.create(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.put('/user', cors(), function(req, res) { + UserManagementAPI.update(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.delete('/user/:username/:domain', cors(), function(req, res) { + UserManagementAPI.delete(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = Router; + + + diff --git a/skyquake/framework/core/views/home.ejs b/skyquake/framework/core/views/home.ejs new file mode 100644 index 000000000..2cb2f80cc --- /dev/null +++ b/skyquake/framework/core/views/home.ejs @@ -0,0 +1,5 @@ +<% if (!user) { %> +

Welcome! Please log in.

+<% } else { %> +

Hello, <%= user.username %>. View your default page. TODO: Update link to dashboard

+<% } %> \ No newline at end of file diff --git a/skyquake/framework/core/views/idpconnectfail.ejs b/skyquake/framework/core/views/idpconnectfail.ejs new file mode 100644 index 000000000..7847b3db9 --- /dev/null +++ b/skyquake/framework/core/views/idpconnectfail.ejs @@ -0,0 +1,43 @@ + +
+

+ We are having trouble connecting to the Identity Provider. +

+

+ Please check that it is running and reachable. +

+ +
\ No newline at end of file diff --git a/skyquake/framework/plugin-index.html b/skyquake/framework/plugin-index.html new file mode 100644 index 000000000..b704a3c10 --- /dev/null +++ b/skyquake/framework/plugin-index.html @@ -0,0 +1,13 @@ + + +
\ No newline at end of file diff --git a/skyquake/framework/source/SourceCache.js b/skyquake/framework/source/SourceCache.js new file mode 100644 index 000000000..ded3c9d32 --- /dev/null +++ b/skyquake/framework/source/SourceCache.js @@ -0,0 +1,96 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import appConfiguration from '../utils/appConfiguration' + +let versionKeyPrefix = null; + +const localCache = new class { + get(key) { + let valueAsString = localStorage.getItem(key); + return valueAsString ? JSON.parse(valueAsString) : undefined; + } + set(key, val) { + localStorage.setItem(key, typeof val === 'string' ? val : JSON.stringify(val)); + } +}(); + +let objCache = new Map(); + +const storeCache = new class { + get(key) { + if (objCache[key]) { + objCache[key].timerId && clearTimeout(objCache[key].timerId) + objCache[key].timerId = setTimeout((key) => delete objCache[key], 2000) + return objCache[key].value; + } + const obj = localCache.get(key); + if (obj) { + objCache[key] = { + value: obj, + timerId: setTimeout((key) => delete objCache[key], 2000) + } + return obj; + } + } + set(key, obj) { + setTimeout(localCache.set, 100, key, obj); + objCache[key] = { + value: obj, + timerId: setTimeout((key) => delete objCache[key], 2000) + } + } + init(version) { + versionKeyPrefix = 's-v-' + version; + const currentStoreVersion = localStorage.getItem('store-version'); + if (currentStoreVersion !== version) { + let removeItems = []; + for (let i = 0; i < localStorage.length; ++i) { + let key = localStorage.key(i); + if (key.startsWith('s-v-')) { + removeItems.push(key); + } + } + removeItems.forEach((key) => localStorage.removeItem(key)); + localStorage.setItem('store-version', version); + } + } +}(); + +class StoreCache { + constructor(name) { + this.name = 's-v-' + name; + } + get(key) { + return storeCache.get(this.name + key); + } + set(key, obj) { + storeCache.set(this.name + key, obj); + } + init() { + return versionKeyPrefix ? Promise.resolve() : new Promise( + (resolve, reject) => { + appConfiguration.get().then((config) => { + storeCache.init(config.version); + resolve(); + }) + } + ) + } +} + +module.exports = StoreCache; \ No newline at end of file diff --git a/skyquake/framework/source/model/index.js b/skyquake/framework/source/model/index.js new file mode 100644 index 000000000..019ada0a9 --- /dev/null +++ b/skyquake/framework/source/model/index.js @@ -0,0 +1,7 @@ +import modelActions from './modelActions' +import modelSource from './modelSource' + +export { + modelSource, + modelActions +} \ No newline at end of file diff --git a/skyquake/framework/source/model/modelActions.js b/skyquake/framework/source/model/modelActions.js new file mode 100644 index 000000000..41dbc00e4 --- /dev/null +++ b/skyquake/framework/source/model/modelActions.js @@ -0,0 +1,31 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import Alt from 'widgets/skyquake_container/skyquakeAltInstance'; + +class Actions { + + constructor() { + this.generateActions( + 'loadModel', + 'processRequestSuccess', + 'processRequestInitiated', + 'processRequestFailure'); + } +} + +export default Alt.createActions(Actions); diff --git a/skyquake/framework/source/model/modelSource.js b/skyquake/framework/source/model/modelSource.js new file mode 100644 index 000000000..3779aa5ca --- /dev/null +++ b/skyquake/framework/source/model/modelSource.js @@ -0,0 +1,128 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import rw from 'utils/rw' +import modelActions from './modelActions' +import Utils from 'utils/utils' +import $ from 'jquery'; + +const source = { + loadModel: function () { + return { + remote: function (state, path) { + return new Promise(function (resolve, reject) { + $.ajax({ + url: '/model?path=' + path, + type: 'GET', + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + updateModel: function () { + return { + remote: function (state, path, data) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'PATCH', + data: data, + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + createModel: function () { + return { + remote: function (state, path, data) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'PUT', + data: data, + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + + deleteModel: function () { + return { + remote: function (state, path) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'DELETE', + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + } +} +module.exports = source; \ No newline at end of file diff --git a/skyquake/framework/source/schema/index.js b/skyquake/framework/source/schema/index.js new file mode 100644 index 000000000..95b152e29 --- /dev/null +++ b/skyquake/framework/source/schema/index.js @@ -0,0 +1,7 @@ +import schemaActions from './schemaActions' +import schemaSource from './schemaSource' + +export { + schemaSource, + schemaActions +} \ No newline at end of file diff --git a/skyquake/framework/source/schema/schemaActions.js b/skyquake/framework/source/schema/schemaActions.js new file mode 100644 index 000000000..7e36cca20 --- /dev/null +++ b/skyquake/framework/source/schema/schemaActions.js @@ -0,0 +1,31 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import Alt from 'widgets/skyquake_container/skyquakeAltInstance'; + +class Actions { + + constructor() { + this.generateActions( + 'loadSchema', + 'loadSchemaSuccess', + 'loadSchemaLoading', + 'loadSchemaFail'); + } +} + +export default Alt.createActions(Actions); diff --git a/skyquake/framework/source/schema/schemaSource.js b/skyquake/framework/source/schema/schemaSource.js new file mode 100644 index 000000000..2c7bd5bcb --- /dev/null +++ b/skyquake/framework/source/schema/schemaSource.js @@ -0,0 +1,95 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import rw from 'utils/rw' +import schemaActions from './schemaActions' +import Utils from 'utils/utils' +import $ from 'jquery'; +import StoreCache from '../SourceCache' + +const storeCache = new StoreCache('schema'); +storeCache.init(); // get the ball rolling + +function getCachedSchema(request) { + const cachedSchema = {}; + const requestSchema = []; + request.forEach((path) => { + let schema = storeCache.get(path); + if (schema) { + cachedSchema[path] = schema + } else { + requestSchema.push(path); + } + }); + return { + cachedSchema, + requestSchema + }; +} + +const schemaSource = { + loadSchema: function () { + return { + local: function (state, request) { + request = Array.isArray(request) ? request : [request]; + const results = getCachedSchema(request); + if (!results.requestSchema.length) { + return(Promise.resolve(results.cachedSchema)); + } + }, + + remote: function (state, request) { + return new Promise(function (resolve, reject) { + storeCache.init().then(() => { + request = Array.isArray(request) ? request : [request]; + const results = getCachedSchema(request); + if (!results.requestSchema.length) { + resolve({ + schema: results.cachedSchema + }); + } else { + $.ajax({ + url: '/schema?request=' + results.requestSchema.join(','), + type: 'GET', + success: function ({ + schema + }) { + for (let path in schema) { + storeCache.set(path, schema[path]); + } + resolve(getCachedSchema(request).cachedSchema); + }, + error: function (error) { + console.log("There was an error getting the schema: ", error); + reject(error); + } + }).fail(function (xhr) { + console.log("There was an error getting the schema: ", xhr); + Utils.checkAuthentication(xhr.status); + reject("There was an error getting the schema.") + }); + } + }) + }) + }, + success: schemaActions.loadSchemaSuccess, + loading: schemaActions.loadSchemaLoading, + error: schemaActions.loadSchemaFail + } + }, +} +export default schemaSource; \ No newline at end of file diff --git a/skyquake/framework/style/_colors.scss b/skyquake/framework/style/_colors.scss index 9378984ba..a01c293f3 100644 --- a/skyquake/framework/style/_colors.scss +++ b/skyquake/framework/style/_colors.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ $body-color:$lightest-gray; $error-red:#FF5F5F; -//PC +/*PC*/ $black: #000; $gray-lightest: #f1f1f1; @@ -42,9 +42,9 @@ $gray-dark: #999; $gray-darker: #666; $gray-darkest: #333; $white: #FFF; -// -// Brand Colors -// +/**/ +/* Brand Colors*/ +/**/ $brand-blue-light: #30baef; $brand-blue: #00acee; $brand-blue-dark: #147ca3; @@ -66,11 +66,12 @@ $neutral-light-3: hsl(360, 100%, 50%); $neutral-light-4: hsl(360, 100%, 50%); $neutral-light-5: hsl(360, 100%, 50%); -$neutral-dark-1: hsl(360, 100%, 50%); -$neutral-dark-2: hsl(360, 100%, 50%); -$neutral-dark-3: hsl(360, 100%, 50%); -$neutral-dark-4: hsl(360, 100%, 50%); -$neutral-dark-5: hsl(360, 100%, 50%); +$neutral-dark-0: hsl(0, 0%, 80.7%); +$neutral-dark-1: hsl(0, 0%, 63.7%); +$neutral-dark-2: hsl(0, 0%, 56.7%); +$neutral-dark-3: hsl(0, 0%, 49.7%); +$neutral-dark-4: hsl(0, 0%, 42.7%); +$neutral-dark-5: hsl(0, 0%, 35.7%); $netral-black: hsl(0, 100%, 0%); diff --git a/skyquake/framework/style/core.css b/skyquake/framework/style/core.css index 01b728b47..e9d5de9cf 100644 --- a/skyquake/framework/style/core.css +++ b/skyquake/framework/style/core.css @@ -16,8 +16,6 @@ * */ /*@import "./vendor/css-reset-2.0/css-reset.css";*/ -@import "../../node_modules/reset-css/reset.css"; - .has-drop-shadow { box-shadow: 2px 2px rgba(0, 0, 0, .15) } @@ -2023,7 +2021,7 @@ a[role=button] { margin-top: 150px; margin-bottom: 20px; background-size: 154px 102px; - /*background-image: url(./img/header-logo.png)*/ + /*background-image: url(./img/header-logo.png)*/ background-image: url(./img/svg/osm-logo_color_rgb.svg); } .login-cntnr .riftio { diff --git a/skyquake/framework/style/img/osm_header.png b/skyquake/framework/style/img/osm_header.png index 53a950063..c67dca3fa 100644 Binary files a/skyquake/framework/style/img/osm_header.png and b/skyquake/framework/style/img/osm_header.png differ diff --git a/skyquake/framework/style/img/osm_header_253x50.png b/skyquake/framework/style/img/osm_header_253x50.png index 10054312e..3055122bf 100644 Binary files a/skyquake/framework/style/img/osm_header_253x50.png and b/skyquake/framework/style/img/osm_header_253x50.png differ diff --git a/skyquake/framework/style/img/osm_header_506x100.png b/skyquake/framework/style/img/osm_header_506x100.png index 7ece8458b..4b7a34477 100644 Binary files a/skyquake/framework/style/img/osm_header_506x100.png and b/skyquake/framework/style/img/osm_header_506x100.png differ diff --git a/skyquake/framework/utils/appConfiguration.js b/skyquake/framework/utils/appConfiguration.js new file mode 100644 index 000000000..28823097f --- /dev/null +++ b/skyquake/framework/utils/appConfiguration.js @@ -0,0 +1,42 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import $ from 'jquery'; + +const configPromise = new Promise((resolve, reject) => { + $.ajax({ + url: '/app-config', + type: 'GET', + success: function (data) { + if (data.version.endsWith('.1')){ + data.version = '' + Date.now(); + } + resolve(data); + }, + error: function (error) { + console.log("There was an error getting config: ", error); + reject(error); + } + }).fail(function (xhr) { + console.log('There was an xhr error getting the config', xhr); + reject(xhr); + }); +}); + +module.exports = { + get: () => configPromise +}; \ No newline at end of file diff --git a/skyquake/framework/utils/roleConstants.js b/skyquake/framework/utils/roleConstants.js new file mode 100644 index 000000000..5ac4e641e --- /dev/null +++ b/skyquake/framework/utils/roleConstants.js @@ -0,0 +1,31 @@ +var c = {}; + +c.PLATFORM = { + OPER: "rw-rbac-platform:platform-oper", + ADMIN: "rw-rbac-platform:platform-admin", + SUPER: "rw-rbac-platform:super-admin" +} + +c.PROJECT = { + TYPE: { + "rw-project-mano:catalog-oper": "rw-project-mano", + "rw-project-mano:catalog-admin": "rw-project-mano", + "rw-project-mano:lcm-oper": "rw-project-mano", + "rw-project-mano:lcm-admin": "rw-project-mano", + "rw-project-mano:account-oper": "rw-project-mano", + "rw-project-mano:account-admin": "rw-project-mano", + "rw-project:project-oper": "rw-project", + "rw-project:project-admin": "rw-project" + }, + CATALOG_OPER: "rw-project-mano:catalog-oper", + CATALOG_ADMIN: "rw-project-mano:catalog-admin", + LCM_OPER: "rw-project-mano:lcm-oper", + LCM_ADMIN: "rw-project-mano:lcm-admin", + ACCOUNT_OPER: "rw-project-mano:account-oper", + ACCOUNT_ADMIN: "rw-project-mano:account-admin", + PROJECT_OPER: "rw-project:project-oper", + PROJECT_ADMIN: "rw-project:project-admin" + +} + +module.exports = c; diff --git a/skyquake/framework/utils/utils.js b/skyquake/framework/utils/utils.js index 7b93fd580..88ef939cb 100644 --- a/skyquake/framework/utils/utils.js +++ b/skyquake/framework/utils/utils.js @@ -15,13 +15,14 @@ * limitations under the License. * */ -//Login needs to be refactored. Too many cross dependencies -var AuthActions = require('../widgets/login/loginAuthActions.js'); -var $ = require('jquery'); -import rw from './rw.js'; +import AuthActions from '../widgets/login/loginAuthActions'; +import $ from 'jquery'; +import rw from './rw'; +import appConfiguration from './appConfiguration' +import SockJS from 'sockjs-client'; + var API_SERVER = rw.getSearchParams(window.location).api_server; let NODE_PORT = rw.getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000); -var SockJS = require('sockjs-client'); var Utils = {}; @@ -29,25 +30,6 @@ Utils.DescriptorModelMeta = null; var INACTIVITY_TIMEOUT = 600000; -Utils.getInactivityTimeout = function() { - return new Promise(function(resolve, reject) { - $.ajax({ - url: '/inactivity-timeout', - type: 'GET', - success: function(data) { - resolve(data); - }, - error: function(error) { - console.log("There was an error getting the inactivity-timeout: ", error); - reject(error); - } - }).fail(function(xhr) { - console.log('There was an xhr error getting the inactivity-timeout', xhr); - reject(xhr); - }); - }); -}; - Utils.isMultiplexerLoaded = function() { if (window.multiplexer) { return true; @@ -75,14 +57,19 @@ Utils.setupMultiplexClient = function() { loadChecker(); }; -Utils.checkAndResolveSocketRequest = function(data, resolve, reject) { +Utils.checkAndResolveSocketRequest = function(data, resolve, reject, successCallback) { const checker = () => { if (!Utils.isMultiplexerLoaded()) { setTimeout(() => { checker(); }, 500); } else { - resolve(data.id); + if (!successCallback) { + resolve(data.id); + } else { + //resolve handled in callback + successCallback(data.id) + } } }; @@ -90,9 +77,8 @@ Utils.checkAndResolveSocketRequest = function(data, resolve, reject) { }; Utils.bootstrapApplication = function() { - var self = this; - return new Promise(function(resolve, reject) { - Promise.all([self.getInactivityTimeout()]).then(function(results) { + return new Promise((resolve, reject) => { + Promise.all([appConfiguration.get()]).then(function(results) { INACTIVITY_TIMEOUT = results[0]['inactivity-timeout']; resolve(); }, function(error) { @@ -129,8 +115,9 @@ Utils.getDescriptorModelMeta = function() { } Utils.addAuthorizationStub = function(xhr) { - var Auth = window.sessionStorage.getItem("auth"); - xhr.setRequestHeader('Authorization', 'Basic ' + Auth); + // NO-OP now that we are dealing with it on the server + // var Auth = window.sessionStorage.getItem("auth"); + // xhr.setRequestHeader('Authorization', 'Basic ' + Auth); }; Utils.getByteDataWithUnitPrefix = function(number, precision) { @@ -183,36 +170,30 @@ Utils.getPacketDataWithUnitPrefix = function(number, precision) { } } Utils.loginHash = "#/login"; -Utils.setAuthentication = function(username, password, cb) { - var self = this; - var AuthBase64 = btoa(username + ":" + password); - window.sessionStorage.setItem("auth", AuthBase64); - self.detectInactivity(); - $.ajax({ - url: '//' + window.location.hostname + ':' + window.location.port + '/check-auth?api_server=' + API_SERVER, - type: 'GET', - beforeSend: Utils.addAuthorizationStub, - success: function(data) { - //console.log("LoggingSource.getLoggingConfig success call. data=", data); - if (cb) { - cb(); - }; - }, - error: function(data) { - Utils.clearAuthentication(); - } - }); -} -Utils.clearAuthentication = function(callback) { + +Utils.clearAuthentication = function() { var self = this; window.sessionStorage.removeItem("auth"); AuthActions.notAuthenticated(); window.sessionStorage.setItem("locationRefHash", window.location.hash); - if (callback) { - callback(); - } else { - window.location.hash = Utils.loginHash; - } + var reloadURL = ''; + $.ajax({ + url: '//' + window.location.hostname + ':' + window.location.port + '/session?api_server=' + API_SERVER + '&hash=' + encodeURIComponent(window.location.hash), + type: 'DELETE', + success: function(data) { + console.log('User logged out'); + reloadURL = data['url'] + '?post_logout_redirect_uri=' + + window.location.protocol + '//' + + window.location.hostname + ':' + + window.location.port + + '/?api_server=' + API_SERVER; + + window.location.replace(reloadURL); + }, + error: function(data) { + console.log('Problem logging user out'); + } + }); } Utils.isNotAuthenticated = function(windowLocation, callback) { var self = this; @@ -322,4 +303,24 @@ Utils.parseError = (error) => { return displayMsg } +Utils.rpcError = (rpcResult) => { + try { + let info = JSON.parse(rpcResult); + let rpcError = info.body || info.errorMessage.body || info.errorMessage.error; + if (rpcError) { + if (typeof rpcError === 'string') { + const index = rpcError.indexOf('{'); + if (index >= 0) { + return JSON.parse(rpcError.substr(index)); + } + } else { + return rpcError; + } + } + } catch (e) { + } + console.log('invalid rpc error: ', rpcResult); + return null; +} + module.exports = Utils; diff --git a/skyquake/framework/widgets/button/button.scss b/skyquake/framework/widgets/button/button.scss index c972e147a..043a769ac 100644 --- a/skyquake/framework/widgets/button/button.scss +++ b/skyquake/framework/widgets/button/button.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,15 +62,18 @@ button{ ############################################################################ */ .SqButton { - align-items: center; + -ms-flex-align: center; + align-items: center; border-style: solid; border-radius: 3px; border-width: 0px; cursor: pointer; + display: -ms-inline-flexbox; display: inline-flex; font-size: 1rem; height: 50px; - justify-content: center; + -ms-flex-pack: center; + justify-content: center; margin: 0 10px; outline: none; padding: 0 15px; @@ -107,8 +110,9 @@ button{ /* Focus */ &:focus { - // box-shadow: $focus-shadow; - border: 1px solid red; + /* box-shadow: $focus-shadow;*/ + border: 1px solid; + border-color: darken($normalHoverBackground, 10%); } /* SIZES @@ -256,11 +260,14 @@ button{ fill: $primaryForeground; } } - - } - +.sqButtonGroup { + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; +} diff --git a/skyquake/framework/widgets/button/sq-button.jsx b/skyquake/framework/widgets/button/sq-button.jsx index ae9312845..8d0ec7702 100644 --- a/skyquake/framework/widgets/button/sq-button.jsx +++ b/skyquake/framework/widgets/button/sq-button.jsx @@ -36,17 +36,35 @@ export default class SqButton extends React.Component { Class += " is-disabled"; } return ( -
+
- {svgHTML} -
{label}
+ {svgHTML} +
{label}
+
+ ) + } +} + +export class ButtonGroup extends React.Component { + render() { + let className = "sqButtonGroup"; + if (this.props.className) { + className = `${className} ${this.props.className}` + } + return ( +
+ {this.props.children}
) } } + SqButton.defaultProps = { + onClick: function(e) { + console.log('Clicked') + }, icon: false, primary: false, disabled: false, diff --git a/skyquake/framework/widgets/components.js b/skyquake/framework/widgets/components.js index f38719aee..461a7834a 100644 --- a/skyquake/framework/widgets/components.js +++ b/skyquake/framework/widgets/components.js @@ -26,355 +26,7 @@ export default { // Histogram: Histogram, Multicomponent: require('./multicomponent/multicomponent.js'), Mixins: require('./mixins/ButtonEventListener.js'), - // Gauge: require('./gauge/gauge.js'), +// Gauge: require('./gauge/gauge.js'), Bullet: require('./bullet/bullet.js') }; -// require('../../assets/js/n3-line-chart.js'); -// var Gauge = require('../../assets/js/gauge-modified.js'); -// var bulletController = function($scope, $element) { -// this.$element = $element; -// this.vertical = false; -// this.value = 0; -// this.min = 0; -// this.max = 100; -// //this.range = this.max - this.min; -// //this.percent = (this.value - this.min) / this.range; -// this.displayValue = this.value; -// this.isPercent = (this.units == '')? true:false; -// this.bulletColor = "#6BB814"; -// this.fontsize = 28; -// this.radius = 4; -// this.containerMarginX = 0; -// this.containerMarginY = 0; -// this.textMarginX = 5; -// this.textMarginY = 42; -// this.bulletMargin = 0; -// this.width = 512; -// this.height = 64; -// this.markerX = -100; // puts it off screen unless set -// var self = this; -// if (this.isPercent) { -// this.displayValue + "%"; -// } -// $scope.$watch( -// function() { -// return self.value; -// }, -// function() { -// self.valueChanged(); -// } -// ); - -// } - -// bulletController.prototype = { - -// valueChanged: function() { -// var range = this.max - this.min; -// var normalizedValue = (this.value - this.min) / range; -// if (this.isPercent) { -// this.displayValue = String(Math.round(normalizedValue * 100)) + "%"; -// } else { -// this.displayValue = this.value; -// } -// // All versions of IE as of Jan 2015 does not support inline CSS transforms on SVG -// if (platform.name == 'IE') { -// this.bulletWidth = Math.round(100 * normalizedValue) + '%'; -// } else { -// this.bulletWidth = this.width - (2 * this.containerMarginX); -// var transform = 'scaleX(' + normalizedValue + ')'; -// var bullet = $(this.$element).find('.bullet2'); -// bullet.css('transform', transform); -// bullet.css('-webkit-transform', transform); -// } -// }, - -// markerChanged: function() { -// var range = this.max - this.min; -// var w = this.width - (2 * this.containerMarginX); -// this.markerX = this.containerMarginX + ((this.marker - this.min) / range ) * w; -// this.markerY1 = 7; -// this.markerY2 = this.width - 7; -// } -// } - -// angular.module('components', ['n3-line-chart']) -// .directive('rwBullet', function() { -// return { -// restrict : 'E', -// templateUrl: 'modules/views/rw.bullet.tmpl.html', -// bindToController: true, -// controllerAs: 'bullet', -// controller: bulletController, -// replace: true, -// scope: { -// min : '@?', -// max : '@?', -// value : '@', -// marker: '@?', -// units: '@?', -// bulletColor: '@?', -// label: '@?' -// } -// }; -// }) -// .directive('rwSlider', function() { -// var controller = function($scope, $element, $timeout) { -// // Q: is there a way to force attributes to be ints? -// $scope.min = $scope.min || "0"; -// $scope.max = $scope.max || "100"; -// $scope.step = $scope.step || "1"; -// $scope.height = $scope.height || "30"; -// $scope.orientation = $scope.orientation || 'horizontal'; -// $scope.tooltipInvert = $scope.tooltipInvert || false; -// $scope.percent = $scope.percent || false; -// $scope.kvalue = $scope.kvalue || false; -// $scope.direction = $scope.direction || "ltr"; -// $($element).noUiSlider({ -// start: parseInt($scope.value), -// step: parseInt($scope.step), -// orientation: $scope.orientation, -// range: { -// min: parseInt($scope.min), -// max: parseInt($scope.max) -// }, -// direction: $scope.direction -// }); -// //$(".no-Ui-target").Link('upper').to('-inline-
') -// var onSlide = function(e, value) { -// $timeout(function(){ -// $scope.value = value; -// }) - -// }; -// $($element).on({ -// change: onSlide, -// slide: onSlide, -// set: $scope.onSet({value: $scope.value}) -// }); -// var val = String(Math.round($scope.value)); -// if ($scope.percent) { -// val += "%" -// } else if ($scope.kvalue) { -// val += "k" -// } -// $($element).height($scope.height); -// if ($scope.tooltipInvert) { -// $($element).find('.noUi-handle').append("
" + val + "
"); -// } else { -// $($element).find('.noUi-handle').append("
" + val + "
"); -// } -// $scope.$watch('value', function(value) { -// var val = String(Math.round($scope.value)); -// if ($scope.percent) { -// val += "%" -// } else if($scope.kvalue) { -// val += "k" -// } -// $($element).val(value); -// $($element).find('.tooltip').html(val); -// if ($scope.tooltipInvert) { -// $($element).find('.tooltip').css('right', $($element).find('.tooltip').innerWidth() * -1); -// } else { -// $($element).find('.tooltip').css('left', $($element).find('.tooltip').innerWidth() * -1); -// } -// }); -// }; - -// return { -// restrict : 'E', -// template: '
', -// controller : controller, -// replace: true, -// scope: { -// min : '@', -// max : '@', -// width: '@', -// height: '@', -// step : '@', -// orientation : '@', -// tooltipInvert: '@', -// percent: '@', -// kvalue: '@?', -// onSet:'&?', -// direction: '@?', -// value:'=?' -// } -// }; -// }) -// .directive('rwGauge', function() { -// return { -// restrict: 'AE', -// template: '', -// replace: true, -// scope: { -// min: '@?', -// max: '@?', -// size: '@?', -// color: '@?', -// value: '@?', -// resize: '@?', -// isAggregate: '@?', -// units: '@?', -// valueFormat: '=?', -// width: '@?' -// }, -// bindToController: true, -// controllerAs: 'gauge', -// controller: function($scope, $element) { -// var self = this; -// this.gauge = null; -// this.min = this.min || 0; -// this.max = this.max || 100; -// this.nSteps = 14; -// this.size = this.size || 300; -// this.units = this.units || ''; -// $scope.width = this.width || 240; -// this.color = this.color || 'hsla(212, 57%, 50%, 1)'; -// if (!this.valueFormat) { -// if (this.max > 1000 || this.value) { -// self.valueFormat = { -// "int": 1, -// "dec": 0 -// }; -// } else { -// self.valueFormat = { -// "int": 1, -// "dec": 2 -// }; -// } -// } -// this.isAggregate = this.isAggregate || false; -// this.resize = this.resize || false; -// if (this.format == 'percent') { -// self.valueFormat = { -// "int": 3, -// "dec": 0 -// }; -// } -// $scope.$watch(function() { -// return self.max; -// }, function(n, o) { -// if(n !== o) { -// renderGauge(); -// } -// }); -// $scope.$watch(function() { -// return self.valueFormat; -// }, function(n, o) { -// if(n != 0) { -// renderGauge(); -// } -// }); -// $scope.$watch(function() { -// return self.value; -// }, function() { -// if (self.gauge) { -// // w/o rounding gauge will unexplainably thrash round. -// self.valueFormat = determineValueFormat(self.value); -// self.gauge.setValue(Math.ceil(self.value * 100) / 100); -// //self.gauge.setValue(Math.round(self.value)); -// } -// }); -// angular.element($element).ready(function() { -// console.log('rendering') -// renderGauge(); -// }) -// window.testme = renderGauge; -// function determineValueFormat(value) { - -// if (value > 999 || self.units == "%") { -// return { -// "int": 1, -// "dec": 0 -// } -// } - -// return { -// "int": 1, -// "dec": 2 -// } -// } -// function renderGauge(calcWidth) { -// if (self.max == self.min) { -// self.max = 14; -// } -// var range = self.max - self.min; -// var step = Math.round(range / self.nSteps); -// var majorTicks = []; -// for (var i = 0; i <= self.nSteps; i++) { -// majorTicks.push(self.min + (i * step)); -// }; -// var redLine = self.min + (range * 0.9); -// var config = { -// isAggregate: self.isAggregate, -// renderTo: angular.element($element)[0], -// width: calcWidth || self.size, -// height: calcWidth || self.size, -// glow: false, -// units: self.units, -// title: false, -// minValue: self.min, -// maxValue: self.max, -// majorTicks: majorTicks, -// valueFormat: determineValueFormat(self.value), -// minorTicks: 0, -// strokeTicks: false, -// highlights: [], -// colors: { -// plate: 'rgba(0,0,0,0)', -// majorTicks: 'rgba(15, 123, 182, .84)', -// minorTicks: '#ccc', -// title: 'rgba(50,50,50,100)', -// units: 'rgba(50,50,50,100)', -// numbers: '#fff', -// needle: { -// start: 'rgba(255, 255, 255, 1)', -// end: 'rgba(255, 255, 255, 1)' -// } -// } -// }; -// var min = config.minValue; -// var max = config.maxValue; -// var N = 1000; -// var increment = (max - min) / N; -// for (i = 0; i < N; i++) { -// var temp_color = 'rgb(0, 172, 238)'; -// if (i > 0.5714 * N && i <= 0.6428 * N) { -// temp_color = 'rgb(0,157,217)'; -// } else if (i >= 0.6428 * N && i < 0.7142 * N) { -// temp_color = 'rgb(0,142,196)'; -// } else if (i >= 0.7142 * N && i < 0.7857 * N) { -// temp_color = 'rgb(0,126,175)'; -// } else if (i >= 0.7857 * N && i < 0.8571 * N) { -// temp_color = 'rgb(0,122,154)'; -// } else if (i >= 0.8571 * N && i < 0.9285 * N) { -// temp_color = 'rgb(0,96,133)'; -// } else if (i >= 0.9285 * N) { -// temp_color = 'rgb(0,80,112)'; -// } -// config.highlights.push({ -// from: i * increment, -// to: increment * (i + 2), -// color: temp_color -// }) -// } -// var updateSize = _.debounce(function() { -// config.maxValue = self.max; -// var clientWidth = self.parentNode.parentNode.clientWidth / 2; -// var calcWidth = (300 > clientWidth) ? clientWidth : 300; -// self.gauge.config.width = self.gauge.config.height = calcWidth; -// self.renderGauge(calcWidth); -// }, 500); -// if (self.resize) $(window).resize(updateSize) -// if (self.gauge) { -// self.gauge.updateConfig(config); -// } else { -// self.gauge = new Gauge(config); -// self.gauge.draw(); -// } -// }; -// }, -// } -// }); diff --git a/skyquake/framework/widgets/form_controls/formControls.jsx b/skyquake/framework/widgets/form_controls/formControls.jsx new file mode 100644 index 000000000..e50fb7ef1 --- /dev/null +++ b/skyquake/framework/widgets/form_controls/formControls.jsx @@ -0,0 +1,113 @@ +import './formControls.jsx'; + +import React from 'react' +import SelectOption from 'widgets/form_controls/selectOption.jsx'; +import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg' +import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg' +import TextInput from 'widgets/form_controls/textInput.jsx'; +import Input from 'widgets/form_controls/input.jsx'; + +export class FormSection extends React.Component { + render() { + let className = 'FormSection ' + this.props.className; + let html = ( +
+
+ {this.props.title} +
+
+ {this.props.children} +
+
+ ); + return html; + } +} + +FormSection.defaultProps = { + className: '' +} + +/** + * AddItemFn: + */ +export class InputCollection extends React.Component { + constructor(props) { + super(props); + this.collection = props.collection; + } + buildTextInput(onChange, v, i) { + return ( + + ) + } + buildSelectOption(initial, options, onChange, v, i) { + return ( + + ); + } + showInput() { + + } + render() { + const props = this.props; + let inputType; + let className = "InputCollection"; + if (props.className) { + className = `${className} ${props.className}`; + } + if (props.type == 'select') { + inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange); + } else { + inputType = this.buildTextInput.bind(this, props.onChange) + } + let html = ( +
+ {props.collection.map((v,i) => { + return ( +
+ {inputType(v, i)} + { + props.readonly ? null : Remove} +
+ ) + })} + { props.readonly ? null : Add} +
+ ); + return html; + } +} + +InputCollection.defaultProps = { + input: Input, + collection: [], + onChange: function(i, e) { + console.log(` + Updating with: ${e.target.value} + At index of: ${i} + `) + }, + AddItemFn: function(e) { + console.log(`Adding a new item to collection`) + }, + RemoveItemFn: function(i, e) { + console.log(`Removing item from collection at index of: ${i}`) + } +} diff --git a/skyquake/framework/widgets/form_controls/formControls.scss b/skyquake/framework/widgets/form_controls/formControls.scss index 4a8843501..ad95add98 100644 --- a/skyquake/framework/widgets/form_controls/formControls.scss +++ b/skyquake/framework/widgets/form_controls/formControls.scss @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ * limitations under the License. * */ -@import 'style/_colors.scss'; +@import '../../style/_colors.scss'; .sqTextInput { display: -ms-flexbox; @@ -32,9 +32,9 @@ color:$darker-gray; text-transform:uppercase; } - input, .readonly, textarea { + input, textarea { height: 35px; - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff; + /* box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;*/ font-size: 1rem; display: block; background: white !important; @@ -49,6 +49,7 @@ .readonly { line-height: 35px; box-shadow:none; + background:none !important; } textarea { -ms-flex-align: stretch; @@ -57,4 +58,107 @@ border:0px; height: 100%; } + &.checkbox { + -ms-flex-direction:row; + flex-direction:row; + -ms-flex-align:center; + align-items:center; + margin-bottom:0; + >span { + -ms-flex-order: 1; + order: 1; + padding-left:1rem; + } + >input { + -ms-flex-order: 0; + order: 0; + + box-shadow:none; + height:25px; + } + } + .invalid { + color: red; + font-weight:strong; + } + input:invalid { + border: 2px solid red; + &:after { + content: 'Invalid Value' + } + } +} + +.sqCheckBox { + display:-ms-flexbox; + display:flex; + label { + display:-ms-flexbox; + display:flex; + -ms-flex-align: center; + align-items: center; + input { + box-shadow: none; + height: auto; + margin: 0 0.25rem; + } + } +} + +.FormSection { + &-title { + color: #000; + background: lightgray; + padding: 0.5rem; + border-top: 1px solid #f1f1f1; + border-bottom: 1px solid #f1f1f1; + } + &-body { + padding: 0.5rem 0.75rem; + } + label { + -ms-flex: 1 0; + flex: 1 0; + } + /* label {*/ + /* display: -ms-flexbox;*/ + /* display: flex;*/ + /* -ms-flex-direction: column;*/ + /* flex-direction: column;*/ + /* width: 100%;*/ + /* margin: 0.5rem 0;*/ + /* -ms-flex-align: start;*/ + /* align-items: flex-start;*/ + /* -ms-flex-pack: start;*/ + /* justify-content: flex-start;*/ + /* }*/ + select { + font-size: 1rem; + min-width: 75%; + height: 35px; + } +} + + + + +.InputCollection { + display:-ms-flexbox; + display:flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -ms-flex-align: center; + align-items: center; + button { + padding: 0.25rem; + height: 1.5rem; + font-size: 0.75rem; + } + select { + min-width: 100%; + } + margin-bottom:0.5rem; + &-wrapper { + + } } diff --git a/skyquake/framework/widgets/form_controls/input.jsx b/skyquake/framework/widgets/form_controls/input.jsx new file mode 100644 index 000000000..ca31c13cd --- /dev/null +++ b/skyquake/framework/widgets/form_controls/input.jsx @@ -0,0 +1,134 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import './formControls.scss'; +import SelectOption from 'widgets/form_controls/selectOption.jsx'; +import CheckSVG from '../../../node_modules/open-iconic/svg/check.svg' +import React, {Component} from 'react'; + +export default class Input extends Component { + render() { + let {label, value, defaultValue, ...props} = this.props; + let inputProperties = { + value: value + } + let isRequired; + let inputType; + let tester = null; + let className = `sqTextInput ${props.className}`; + + if(this.props.required) { + isRequired = * + } + if (defaultValue) { + inputProperties.defaultValue = defaultValue; + } + if (props.pattern) { + inputProperties.pattern = props.pattern; + tester = new RegExp(props.pattern); + } + if(props.hasOwnProperty('type') && (props.type.toLowerCase() == 'checkbox')) { + inputProperties.checked = props.checked; + className = `${className} checkbox`; + } + if (value == undefined) { + value = defaultValue; + } + switch(props.type) { + case 'textarea': + inputType =