Allow open from template button when lacking source

GitOrigin-RevId: 8fd49bff6cc0f66d041bb70f9345b2744978be3a
This commit is contained in:
Douglas Lovell 2018-11-28 12:51:24 -03:00 committed by sharelatex
parent ee800f7448
commit ed3147a58f
44 changed files with 148 additions and 538 deletions

View file

@ -132,9 +132,6 @@ compile_full:
$(MAKE) compile_modules_full
$(MAKE) compile # ide.js, main.js, share.js, and anything missed
compile_css_full:
$(MAKE) css_full
compile_modules: $(MODULE_MAKEFILES)
@set -e; \
for dir in $(MODULE_DIRS); \

View file

@ -203,7 +203,7 @@ module.exports = AuthenticationController =
return next()
else
logger.log url:req.url, "user trying to access endpoint not in global whitelist"
AuthenticationController.setRedirectInSession(req)
AuthenticationController._setRedirectInSession(req)
return res.redirect "/login"
httpAuth: basicAuth (user, pass)->
@ -212,16 +212,6 @@ module.exports = AuthenticationController =
logger.err user:user, pass:pass, "invalid login details"
return isValid
setRedirectInSession: (req, value) ->
if !value?
value = if Object.keys(req.query).length > 0 then "#{req.path}?#{querystring.stringify(req.query)}" else "#{req.path}"
if (
req.session? &&
!/^\/(socket.io|js|stylesheets|img)\/.*$/.test(value) &&
!/^.*\.(png|jpeg|svg)$/.test(value)
)
req.session.postLoginRedirect = value
_redirectToLoginOrRegisterPage: (req, res)->
if (req.query.zipUrl? or req.query.project_name? or req.path == '/user/subscription/new')
return AuthenticationController._redirectToRegisterPage(req, res)
@ -230,14 +220,14 @@ module.exports = AuthenticationController =
_redirectToLoginPage: (req, res) ->
logger.log url: req.url, "user not logged in so redirecting to login page"
AuthenticationController.setRedirectInSession(req)
AuthenticationController._setRedirectInSession(req)
url = "/login?#{querystring.stringify(req.query)}"
res.redirect url
Metrics.inc "security.login-redirect"
_redirectToRegisterPage: (req, res) ->
logger.log url: req.url, "user not logged in so redirecting to register page"
AuthenticationController.setRedirectInSession(req)
AuthenticationController._setRedirectInSession(req)
url = "/register?#{querystring.stringify(req.query)}"
res.redirect url
Metrics.inc "security.login-redirect"
@ -255,6 +245,16 @@ module.exports = AuthenticationController =
Metrics.inc "user.login.failed"
callback()
_setRedirectInSession: (req, value) ->
if !value?
value = if Object.keys(req.query).length > 0 then "#{req.path}?#{querystring.stringify(req.query)}" else "#{req.path}"
if (
req.session? &&
!/^\/(socket.io|js|stylesheets|img)\/.*$/.test(value) &&
!/^.*\.(png|jpeg|svg)$/.test(value)
)
req.session.postLoginRedirect = value
_getRedirectFromSession: (req) ->
return req?.session?.postLoginRedirect || null

View file

@ -117,5 +117,5 @@ module.exports = AuthorizationMiddlewear =
logger.log {from: from}, "redirecting to login"
redirect_to = "/login"
if from?
AuthenticationController.setRedirectInSession(req, from)
AuthenticationController._setRedirectInSession(req, from)
res.redirect redirect_to

View file

@ -270,7 +270,7 @@ module.exports = ProjectController =
project: (cb)->
ProjectGetter.getProject(
project_id,
{ name: 1, lastUpdated: 1, track_changes: 1, owner_ref: 1, brandVariationId: 1, overleaf: 1 },
{ name: 1, lastUpdated: 1, track_changes: 1, owner_ref: 1, brandVariationId: 1, 'overleaf.history.display': 1 },
cb
)
user: (cb)->
@ -323,9 +323,6 @@ module.exports = ProjectController =
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
allowedFreeTrial = !!subscription.freeTrial.allowed || true
showGitBridge =
user.betaProgram && !project.overleaf?.id? # don't support v1 projects yet
logger.log project_id:project_id, "rendering editor page"
res.render 'project/editor',
title: project.name
@ -373,7 +370,7 @@ module.exports = ProjectController =
brandVariation: brandVariation
allowedImageNames: Settings.allowedImageNames || []
gitBridgePublicBaseUrl: Settings.gitBridgePublicBaseUrl
showGitBridge: showGitBridge
showGitBridge: req.query?.gitbridge == 'true' || user.isAdmin
timer.done()
_buildProjectList: (allProjects, v1Projects = [])->

View file

@ -74,7 +74,13 @@ module.exports = ProjectDetailsHandler =
if arguments.length is 3 && typeof suffixes is 'function' # make suffixes an optional argument
callback = suffixes
suffixes = []
ProjectDetailsHandler.ensureProjectNameIsUnique user_id, name, suffixes, callback
timestamp = new Date().toISOString().replace(/T(\d+):(\d+):(\d+)\..*/,' $1$2$3') # strip out unwanted characters
ProjectDetailsHandler.ensureProjectNameIsUnique user_id, name, suffixes.concat(" (#{timestamp})"), callback
_addSuffixToProjectName: (name, suffix = '') ->
# append the suffix and truncate the project title if needed
truncatedLength = ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH - suffix.length
return name.substr(0, truncatedLength) + suffix
# FIXME: we should put a lock around this to make it completely safe, but we would need to do that at
# the point of project creation, rather than just checking the name at the start of the import.
@ -100,12 +106,8 @@ module.exports = ProjectDetailsHandler =
candidateName = ProjectDetailsHandler._addSuffixToProjectName(name, suffix)
if isUnique(candidateName)
return callback(null, candidateName, true)
# if there are no (more) suffixes, use a numeric one
uniqueName = ProjectDetailsHandler._addNumericSuffixToProjectName(name, allProjectNames)
if uniqueName?
callback(null, uniqueName, true)
else
callback(new Error("Failed to generate a unique name for file: #{name}"))
# we couldn't make the name unique, something is wrong
return callback new Errors.InvalidNameError("Project name could not be made unique")
fixProjectName: (name) ->
if name == "" || !name
@ -154,30 +156,3 @@ module.exports = ProjectDetailsHandler =
Project.update {_id: project_id}, {$set: {tokens: tokens}}, (err) ->
return callback(err) if err?
callback(null, tokens)
_addSuffixToProjectName: (name, suffix = '') ->
# append the suffix and truncate the project title if needed
truncatedLength = ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH - suffix.length
return name.substr(0, truncatedLength) + suffix
_addNumericSuffixToProjectName: (name, allProjectNames) ->
NUMERIC_SUFFIX_MATCH = / \((\d+)\)$/
suffixedName = (basename, number) ->
suffix = " (#{number})"
return basename.substr(0, ProjectDetailsHandler.MAX_PROJECT_NAME_LENGTH - suffix.length) + suffix
match = name.match(NUMERIC_SUFFIX_MATCH)
basename = name
n = 1
last = allProjectNames.size + n
if match?
basename = name.replace(NUMERIC_SUFFIX_MATCH, '')
n = parseInt(match[1])
while n <= last
candidate = suffixedName(basename, n)
return candidate unless allProjectNames.has(candidate)
n += 1
return null

View file

@ -12,7 +12,6 @@ UserGetter = require "../User/UserGetter"
FeaturesUpdater = require './FeaturesUpdater'
planFeatures = require './planFeatures'
GroupPlansData = require './GroupPlansData'
V1SubscriptionManager = require "./V1SubscriptionManager"
module.exports = SubscriptionController =
@ -98,8 +97,7 @@ module.exports = SubscriptionController =
managedGroupSubscriptions,
confirmedMemberInstitutions,
managedInstitutions,
v1Subscriptions,
v1SubscriptionStatus
v1Subscriptions
} = results
logger.log {
user,
@ -108,8 +106,7 @@ module.exports = SubscriptionController =
managedGroupSubscriptions,
confirmedMemberInstitutions,
managedInstitutions,
v1Subscriptions,
v1SubscriptionStatus
v1Subscriptions
}, "showing subscription dashboard"
plans = SubscriptionViewModelBuilder.buildViewModel()
data = {
@ -121,8 +118,7 @@ module.exports = SubscriptionController =
managedGroupSubscriptions,
confirmedMemberInstitutions,
managedInstitutions,
v1Subscriptions,
v1SubscriptionStatus
v1Subscriptions
}
res.render "subscriptions/dashboard", data
@ -162,15 +158,6 @@ module.exports = SubscriptionController =
return next(err)
res.redirect "/user/subscription"
cancelV1Subscription: (req, res, next) ->
user_id = AuthenticationController.getLoggedInUserId(req)
logger.log {user_id}, "canceling v1 subscription"
V1SubscriptionManager.cancelV1Subscription user_id, (err)->
if err?
logger.err err:err, user_id:user_id, "something went wrong canceling v1 subscription"
return next(err)
res.redirect "/user/subscription"
updateSubscription: (req, res, next)->
_origin = req?.query?.origin || null
user = AuthenticationController.getSessionUser(req)

View file

@ -40,8 +40,6 @@ module.exports =
webRouter.post '/user/subscription/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
webRouter.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
webRouter.post '/user/subscription/v1/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelV1Subscription
webRouter.put '/user/subscription/extend', AuthenticationController.requireLogin(), SubscriptionController.extendTrial
webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage

View file

@ -29,7 +29,13 @@ module.exports = SubscriptionUpdater =
@addUsersToGroup(subscriptionId, [userId], callback)
addUsersToGroup: (subscriptionId, memberIds, callback)->
@addUsersToGroupWithoutFeaturesRefresh subscriptionId, memberIds, (err) ->
logger.log subscriptionId: subscriptionId, memberIds: memberIds, "adding members into mongo subscription"
searchOps =
_id: subscriptionId
insertOperation =
{ $addToSet: { member_ids: { $each: memberIds } } }
Subscription.findAndModify searchOps, insertOperation, (err, subscription) ->
return callback(err) if err?
# Only apply features updates to users, not user stubs
@ -39,14 +45,6 @@ module.exports = SubscriptionUpdater =
userIds = users.map (u) -> u._id.toString()
async.map userIds, FeaturesUpdater.refreshFeatures, callback
addUsersToGroupWithoutFeaturesRefresh: (subscriptionId, memberIds, callback)->
logger.log subscriptionId: subscriptionId, memberIds: memberIds, "adding members into mongo subscription"
searchOps =
_id: subscriptionId
insertOperation =
{ $addToSet: { member_ids: { $each: memberIds } } }
Subscription.findAndModify searchOps, insertOperation, callback
removeUserFromGroup: (subscriptionId, user_id, callback)->
searchOps =

View file

@ -51,10 +51,6 @@ module.exports =
return cb(error) if error?
# Only return one argument to async.auto, otherwise it returns an array
cb(null, subscriptions)
v1SubscriptionStatus: (cb) ->
V1SubscriptionManager.getSubscriptionStatusFromV1 user._id, (error, status, v1Id) ->
return cb(error) if error?
cb(null, status)
}, (err, results) ->
return callback(err) if err?
{
@ -64,7 +60,6 @@ module.exports =
confirmedMemberInstitutions,
managedInstitutions,
v1Subscriptions,
v1SubscriptionStatus,
recurlySubscription,
plan
} = results
@ -73,7 +68,6 @@ module.exports =
confirmedMemberInstitutions ?= []
managedInstitutions ?= []
v1Subscriptions ?= {}
v1SubscriptionStatus ?= {}
if personalSubscription?.toObject?
@ -103,8 +97,7 @@ module.exports =
memberGroupSubscriptions,
confirmedMemberInstitutions,
managedInstitutions,
v1Subscriptions,
v1SubscriptionStatus
v1Subscriptions
}
buildViewModel : ->

View file

@ -39,18 +39,6 @@ module.exports = V1SubscriptionManager =
url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscriptions"
}, callback
getSubscriptionStatusFromV1: (userId, callback=(err, status) ->) ->
V1SubscriptionManager._v1Request userId, {
method: 'GET',
url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscription_status"
}, callback
cancelV1Subscription: (userId, callback=(err)->) ->
V1SubscriptionManager._v1Request userId, {
method: 'DELETE',
url: (v1Id) -> "/api/v1/sharelatex/users/#{v1Id}/subscription"
}, callback
v1IdForUser: (userId, callback=(err, v1Id) ->) ->
UserGetter.getUser userId, {'overleaf.id': 1}, (err, user) ->
return callback(err) if err?
@ -88,7 +76,7 @@ module.exports = V1SubscriptionManager =
pass: settings.apis.v1.pass
sendImmediately: true
json: true,
timeout: 15 * 1000
timeout: 5 * 1000
}, (error, response, body) ->
if error?
# Specially handle no connection err, so warning can be shown

View file

@ -21,5 +21,5 @@ module.exports = SudoModeMiddlewear =
return next()
else
logger.log {userId}, "[SudoMode] sudo mode not active, redirecting"
AuthenticationController.setRedirectInSession(req)
AuthenticationController._setRedirectInSession(req)
return res.redirect('/confirm-password')

View file

@ -60,7 +60,7 @@ module.exports = TokenAccessController =
else
logger.log {token, projectId: project._id},
"[TokenAccess] deny anonymous read-and-write token access"
AuthenticationController.setRedirectInSession(req)
AuthenticationController._setRedirectInSession(req)
return res.redirect('/restricted')
if project.owner_ref.toString() == userId
logger.log {userId, projectId: project._id},

View file

@ -51,7 +51,7 @@ module.exports =
# such as being sent from the editor to /login, then set the redirect explicitly
if req.query.redir? and !AuthenticationController._getRedirectFromSession(req)?
logger.log {redir: req.query.redir}, "setting explicit redirect from login page"
AuthenticationController.setRedirectInSession(req, req.query.redir)
AuthenticationController._setRedirectInSession(req, req.query.redir)
res.render 'user/login',
title: 'login',
email: req.query.email

View file

@ -6,39 +6,21 @@ Errors = require('../Errors/Errors')
logger = require("logger-sharelatex")
module.exports =
requireTeamAccess: (req, res, next) ->
requireAccessToEntity('team', req.params.id, req, res, next)
requireEntityAccess: (entityName, entityIdOverride = null) ->
(req, res, next) ->
loggedInUser = AuthenticationController.getSessionUser(req)
unless loggedInUser
return AuthorizationMiddlewear.redirectToRestricted req, res, next
requireGroupAccess: (req, res, next) ->
requireAccessToEntity('group', req.params.id, req, res, next)
entityId = entityIdOverride or req.params.id
getEntity entityName, entityId, loggedInUser, (error, entity, entityConfig) ->
return next(error) if error?
unless entity?
return AuthorizationMiddlewear.redirectToRestricted(req, res, next)
requireGroupManagersAccess: (req, res, next) ->
requireAccessToEntity('groupManagers', req.params.id, req, res, next)
requireInstitutionAccess: (req, res, next) ->
requireAccessToEntity('institution', req.params.id, req, res, next)
requirePublisherAccess: (req, res, next) ->
requireAccessToEntity('publisher', req.params.id, req, res, next)
requireGraphAccess: (req, res, next) ->
requireAccessToEntity(
req.query.resource_type, req.query.resource_id, req, res, next
)
requireAccessToEntity = (entityName, entityId, req, res, next) ->
loggedInUser = AuthenticationController.getSessionUser(req)
unless loggedInUser
return AuthorizationMiddlewear.redirectToRestricted req, res, next
getEntity entityName, entityId, loggedInUser, (error, entity, entityConfig) ->
return next(error) if error?
unless entity?
return AuthorizationMiddlewear.redirectToRestricted(req, res, next)
req.entity = entity
req.entityConfig = entityConfig
next()
req.entity = entity
req.entityConfig = entityConfig
next()
getEntity = (entityName, entityId, userId, callback = (error, entity, entityConfig)->) ->
entityConfig = EntityConfigs[entityName]

View file

@ -62,19 +62,3 @@ module.exports =
pathsFor: (id) ->
addMember: "/manage/institutions/#{id}/managers"
removeMember: "/manage/institutions/#{id}/managers"
publisher:
modelName: 'Publisher'
fields:
primaryKey: 'slug'
read: ['managerIds']
write: 'managerIds'
access: 'managerIds'
name: 'name'
translations:
title: 'publisher_account'
subtitle: 'managers_management'
remove: 'remove_manager'
pathsFor: (id) ->
addMember: "/manage/publishers/#{id}/managers"
removeMember: "/manage/publishers/#{id}/managers"

View file

@ -4,7 +4,6 @@ Errors = require('../Errors/Errors')
EntityModels =
Institution: require('../../models/Institution').Institution
Subscription: require('../../models/Subscription').Subscription
Publisher: require('../../models/Publisher').Publisher
UserMembershipViewModel = require('./UserMembershipViewModel')
UserGetter = require('../User/UserGetter')
logger = require('logger-sharelatex')

View file

@ -5,52 +5,36 @@ TeamInvitesController = require '../Subscription/TeamInvitesController'
module.exports =
apply: (webRouter) ->
# group members routes
webRouter.get '/manage/groups/:id/members',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireEntityAccess('group'),
UserMembershipController.index
webRouter.post '/manage/groups/:id/invites',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireEntityAccess('group'),
TeamInvitesController.createInvite
webRouter.delete '/manage/groups/:id/user/:user_id',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireEntityAccess('group'),
SubscriptionGroupController.removeUserFromGroup
webRouter.delete '/manage/groups/:id/invites/:email',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireEntityAccess('group'),
TeamInvitesController.revokeInvite
webRouter.get '/manage/groups/:id/members/export',
UserMembershipAuthorization.requireGroupAccess,
UserMembershipAuthorization.requireEntityAccess('group'),
UserMembershipController.exportCsv
# group managers routes
webRouter.get "/manage/groups/:id/managers",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipController.index
webRouter.post "/manage/groups/:id/managers",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipController.add
webRouter.delete "/manage/groups/:id/managers/:userId",
UserMembershipAuthorization.requireGroupManagersAccess,
UserMembershipController.remove
# institution members routes
webRouter.get "/manage/institutions/:id/managers",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipController.index
webRouter.post "/manage/institutions/:id/managers",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipController.add
webRouter.delete "/manage/institutions/:id/managers/:userId",
UserMembershipAuthorization.requireInstitutionAccess,
UserMembershipController.remove
regularEntitites =
groups: 'groupManagers'
institutions: 'institution'
for pathName, entityName of regularEntitites
do (pathName, entityName) ->
webRouter.get "/manage/#{pathName}/:id/managers",
UserMembershipAuthorization.requireEntityAccess(entityName),
UserMembershipController.index
# publisher members routes
webRouter.get "/manage/publishers/:id/managers",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipController.index
webRouter.post "/manage/publishers/:id/managers",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipController.add
webRouter.delete "/manage/publishers/:id/managers/:userId",
UserMembershipAuthorization.requirePublisherAccess,
UserMembershipController.remove
webRouter.post "/manage/#{pathName}/:id/managers",
UserMembershipAuthorization.requireEntityAccess(entityName),
UserMembershipController.add
webRouter.delete "/manage/#{pathName}/:id/managers/:userId",
UserMembershipAuthorization.requireEntityAccess(entityName),
UserMembershipController.remove

View file

@ -16,7 +16,6 @@ hashedFiles = {}
Path = require 'path'
Features = require "./Features"
Modules = require "./Modules"
moment = require 'moment'
jsPath =
if Settings.useMinifiedJs
@ -127,7 +126,6 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
res.locals.fullJsPath = Url.resolve(staticFilesBase, jsPath)
res.locals.lib = PackageVersions.lib
res.locals.moment = moment
res.locals.buildJsPath = (jsFile, opts = {})->
path = Path.join(jsPath, jsFile)

View file

@ -1,34 +0,0 @@
mongoose = require 'mongoose'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId
settings = require 'settings-sharelatex'
request = require 'request'
PublisherSchema = new Schema
slug: { type: String, required: true }
managerIds: [ type:ObjectId, ref:'User' ]
# fetch publisher's (brand on v1) data from v1 API. Errors are ignored
PublisherSchema.method 'fetchV1Data', (callback = (error, publisher)->) ->
request {
baseUrl: settings.apis.v1.url
url: "/api/v2/brands/#{this.slug}"
method: 'GET'
auth:
user: settings.apis.v1.user
pass: settings.apis.v1.pass
sendImmediately: true
}, (error, response, body) =>
try parsedBody = JSON.parse(body) catch e
this.name = parsedBody?.name
this.partner = parsedBody?.partner
callback(null, this)
conn = mongoose.createConnection(settings.mongo.url, {
server: {poolSize: settings.mongo.poolSize || 10},
config: {autoIndex: false}
})
Publisher = conn.model 'Publisher', PublisherSchema
exports.Publisher = Publisher
exports.PublisherSchema = PublisherSchema

View file

@ -50,7 +50,6 @@ UserSchema = new Schema
references: { type:Boolean, default: Settings.defaultFeatures.references }
trackChanges: { type:Boolean, default: Settings.defaultFeatures.trackChanges }
mendeley: { type:Boolean, default: Settings.defaultFeatures.mendeley }
zotero: { type:Boolean, default: Settings.defaultFeatures.zotero }
referencesSearch: { type:Boolean, default: Settings.defaultFeatures.referencesSearch }
}
referal_id : {type:String, default:() -> uuid.v4().split("-")[0]}

View file

@ -162,7 +162,7 @@ script(type='text/ng-template', id='newFileModalTemplate')
on-enter="create()",
name="url"
)
.row-spaced-small
.row-spaced.small
label(for="name") File name in this project
input.form-control(
type="text",

View file

@ -351,7 +351,7 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
| <strong>Direct git access to your projects</strong> is not yet available, but you can migrate your project to the Overleaf v2 GitHub integration
|
a(href='https://www.overleaf.com/help/343-working-offline-in-overleaf-v2', target='_blank') Read More.
li There is no <strong>CiteULike</strong> integration
li There are <strong>no Zotero and CiteULike</strong> integrations yet
li Some <strong>Journals and Services in the Submit menu</strong> don't support direct submissions yet
.v1-import-cta
p

View file

@ -35,9 +35,6 @@ block content
-if (settings.overleaf && v1Subscriptions)
include ./dashboard/_v1_subscriptions
-if (v1SubscriptionStatus)
include ./dashboard/_v1_subscription_status
-if (!hasAnySubscription)
p You're on the #{settings.appName} Free plan.
|

View file

@ -5,20 +5,9 @@ each managedGroupSubscription in managedGroupSubscriptions
+teamName(managedGroupSubscription)
p
a.btn.btn-primary(href="/manage/groups/" + managedGroupSubscription._id + "/members")
i.fa.fa-fw.fa-users
| &nbsp;
| Manage members
| &nbsp;
p
a(href="/manage/groups/" + managedGroupSubscription._id + "/managers")
i.fa.fa-fw.fa-users
| &nbsp;
a.btn.btn-primary(href="/manage/groups/" + managedGroupSubscription._id + "/managers")
| Manage group managers
| &nbsp;
p
a(href="/metrics/groups/" + managedGroupSubscription._id)
i.fa.fa-fw.fa-line-chart
| &nbsp;
| View metrics
hr

View file

@ -4,18 +4,6 @@ each institution in managedInstitutions
|
strong= institution.name
p
a.btn.btn-primary(href="/metrics/institutions/" + institution.v1Id)
i.fa.fa-fw.fa-line-chart
| &nbsp;
| View metrics
p
a(href="/institutions/" + institution.v1Id + "/hub")
i.fa.fa-fw.fa-user-circle
| &nbsp;
| View hub
p
a(href="/manage/institutions/" + institution.v1Id + "/managers")
i.fa.fa-fw.fa-users
| &nbsp;
a.btn.btn-primary(href="/manage/institutions/" + institution.v1Id + "/managers")
| Manage institution managers
hr

View file

@ -1,60 +0,0 @@
- if (v1SubscriptionStatus['team'])
p
| You have a legacy group licence from Overleaf v1.
- if (v1SubscriptionStatus['team']['will_end_at'])
p
| Your current group licence ends on
|
strong= moment(v1SubscriptionStatus['team']['will_end_at']).format('Do MMM YY')
|
| and will
|
- if (v1SubscriptionStatus['team']['will_renew'])
| be automatically renewed.
- else
| not be automatically renewed.
- if (v1SubscriptionStatus['can_cancel_team'])
p
form(method="POST", action="/user/subscription/v1/cancel")
input(type="hidden", name="_csrf", value=csrfToken)
button().btn.btn-danger Stop automatic renewal
- else
p
| Please
|
a(href="/contact") contact support
|
| to make changes to your plan
hr
- if (v1SubscriptionStatus['product'])
p
| You have a legacy Overleaf v1
|
strong= v1SubscriptionStatus['product']['display_name']
|
| plan.
p
| Your plan ends on
|
strong= moment(v1SubscriptionStatus['product']['will_end_at']).format('Do MMM YY')
|
| and will
|
- if (v1SubscriptionStatus['product']['will_renew'])
| be automatically renewed.
- else
| not be automatically renewed.
- if (v1SubscriptionStatus['can_cancel'])
p
form(method="POST", action="/user/subscription/v1/cancel")
input(type="hidden", name="_csrf", value=csrfToken)
button().btn.btn-danger Stop automatic renewal
- else
p
| Please
|
a(href="/contact") contact support
|
| to make changes to your plan
hr

View file

@ -1,3 +1,11 @@
- if (v1Subscriptions.has_subscription)
-hasAnySubscription = true
p
| You are subscribed to Overleaf through Overleaf v1
p
a.btn.btn-primary(href='/sign_in_to_v1?return_to=/users/edit%23status') Manage v1 Subscription
hr
- if (v1Subscriptions.teams && v1Subscriptions.teams.length > 0)
-hasAnySubscription = true
for team in v1Subscriptions.teams

View file

@ -17,7 +17,7 @@ services:
PROJECT_HISTORY_ENABLED: 'true'
ENABLED_LINKED_FILE_TYPES: 'url'
LINKED_URL_PROXY: 'http://localhost:6543'
ENABLED_LINKED_FILE_TYPES: 'url,project_file,project_output_file,mendeley,zotero'
ENABLED_LINKED_FILE_TYPES: 'url,project_file,project_output_file,mendeley'
SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee
NODE_ENV: production
depends_on:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

View file

@ -79,8 +79,7 @@ define([
name: file.name,
id: file._id,
type: 'file',
linkedFileData,
created: file.created
linkedFileData
})
return this.recalculateDocList()
})

View file

@ -16,7 +16,7 @@
color: @link-color-alt;
&:focus, &:hover {
background-color: transparent!important;
border: 0;
border: 0!important;
color: @link-hover-color-alt;
}
}
@ -36,4 +36,4 @@
background-color: transparent!important;
border: none!important;
}
}
}

View file

@ -42,11 +42,3 @@
@editor-toggler-hover-bg-color: @ieee-blue;
@toggle-switch-highlight-color: @ieee-blue;
@footer-link-color : @link-color;
@footer-link-hover-color : @link-hover-color;
@navbar-subdued-hover-color : @ieee-blue;
@navbar-default-link-hover-bg : @ieee-blue;
@navbar-default-link-hover-color: @ieee-blue;
@navbar-default-link-active-bg : @ieee-blue;

View file

@ -4,41 +4,3 @@
@is-overleaf : true;
@is-overleaf-light: false;
@show-rich-text : true;
@ieee-wedge: 30px;
body > .portal-ieee {
padding-top: @header-height;
}
.portal-ieee {
.ieee-header {
background-color: @ieee-blue;
margin-bottom: @margin-xl;
padding-bottom: @padding-sm;
padding-top: @padding-sm;
h1 {
margin: 0;
}
.ieee-logo {
width: @navbar-brand-width;
}
}
.ieee-subheader {
background-color: @ieee-blue;
color: #ffffff;
line-height: 1;
padding: @padding-md @ieee-wedge;
position: relative;
&:after {
content: "";
display: block;
position: absolute;
border-style: solid;
left: -1px;
top: -1px;
border-color: @content-alt-bg-color transparent;
border-width: @ieee-wedge @ieee-wedge 0 0;
}
}
}

View file

@ -234,8 +234,7 @@ describe 'Subscriptions', ->
before (done) ->
v1Id = MockV1Api.nextV1Id()
MockV1Api.setUser v1Id, {
subscription: {},
subscription_status: {}
subscription: {}
}
MockV1Api.setAffiliations [{
email: 'confirmed-affiliation-email@stanford.example.edu'
@ -282,10 +281,6 @@ describe 'Subscriptions', ->
name: 'Test team'
}]
}
subscription_status: @subscription_status = {
product: { 'mock': 'product' }
team: null
}
}
@user.setV1Id v1Id, (error) =>
return done(error) if error?
@ -300,29 +295,4 @@ describe 'Subscriptions', ->
expect(@data.memberGroupSubscriptions).to.deep.equal []
it 'should return a v1Subscriptions', ->
expect(@data.v1Subscriptions).to.deep.equal @subscription
it 'should return a v1SubscriptionStatus', ->
expect(@data.v1SubscriptionStatus).to.deep.equal @subscription_status
describe.only 'canceling', ->
before (done) ->
@user = new User()
MockV1Api.setUser v1Id = MockV1Api.nextV1Id(), @v1_user = {}
async.series [
(cb) => @user.login(cb)
(cb) => @user.setV1Id(v1Id, cb)
], (error) =>
@user.request {
method: 'POST',
url: '/user/subscription/v1/cancel'
}, (error, @response) =>
return done(error) if error?
done()
it 'should tell v1 to cancel the subscription', ->
expect(@v1_user.canceled).to.equal true
it 'should redirect to the subscription dashboard', ->
expect(@response.statusCode).to.equal 302
expect(@response.headers.location).to.equal '/user/subscription'
expect(@data.v1Subscriptions).to.deep.equal @subscription

View file

@ -38,9 +38,6 @@ module.exports = MockDocUpdaterApi =
app.post "/project/:project_id/doc/:doc_id", (req, res, next) =>
res.sendStatus 204
app.delete "/project/:project_id", (req, res) =>
res.sendStatus 204
app.post "/project/:project_id/doc/:doc_id/flush", (req, res, next) =>
res.sendStatus 204

View file

@ -53,20 +53,6 @@ module.exports = MockV1Api =
else
res.sendStatus 404
app.get "/api/v1/sharelatex/users/:v1_user_id/subscription_status", (req, res, next) =>
user = @users[req.params.v1_user_id]
if user?.subscription_status?
res.json user.subscription_status
else
res.sendStatus 404
app.delete "/api/v1/sharelatex/users/:v1_user_id/subscription", (req, res, next) =>
user = @users[req.params.v1_user_id]
if user?
user.canceled = true
res.sendStatus 200
else
res.sendStatus 404
app.post "/api/v1/sharelatex/users/:v1_user_id/sync", (req, res, next) =>
@syncUserFeatures(req.params.v1_user_id)

View file

@ -491,10 +491,10 @@ describe "AuthenticationController", ->
beforeEach ->
@req.headers = {}
@AuthenticationController.httpAuth = sinon.stub()
@setRedirect = sinon.spy(@AuthenticationController, 'setRedirectInSession')
@_setRedirect = sinon.spy(@AuthenticationController, '_setRedirectInSession')
afterEach ->
@setRedirect.restore()
@_setRedirect.restore()
describe "with white listed url", ->
beforeEach ->
@ -540,7 +540,7 @@ describe "AuthenticationController", ->
@AuthenticationController.requireGlobalLogin @req, @res, @next
it 'should have called setRedirectInSession', ->
@setRedirect.callCount.should.equal 1
@_setRedirect.callCount.should.equal 1
it "should redirect to the /login page", ->
@res.redirectedTo.should.equal "/login"
@ -640,18 +640,18 @@ describe "AuthenticationController", ->
@callback.called.should.equal true
describe 'setRedirectInSession', ->
describe '_setRedirectInSession', ->
beforeEach ->
@req = {session: {}}
@req.path = "/somewhere"
@req.query = {one: "1"}
it 'should set redirect property on session', ->
@AuthenticationController.setRedirectInSession(@req)
@AuthenticationController._setRedirectInSession(@req)
expect(@req.session.postLoginRedirect).to.equal "/somewhere?one=1"
it 'should set the supplied value', ->
@AuthenticationController.setRedirectInSession(@req, '/somewhere/specific')
@AuthenticationController._setRedirectInSession(@req, '/somewhere/specific')
expect(@req.session.postLoginRedirect).to.equal "/somewhere/specific"
describe 'with a png', ->
@ -659,7 +659,7 @@ describe "AuthenticationController", ->
@req = {session: {}}
it 'should not set the redirect', ->
@AuthenticationController.setRedirectInSession(@req, '/something.png')
@AuthenticationController._setRedirectInSession(@req, '/something.png')
expect(@req.session.postLoginRedirect).to.equal undefined
describe 'with a js path', ->
@ -668,7 +668,7 @@ describe "AuthenticationController", ->
@req = {session: {}}
it 'should not set the redirect', ->
@AuthenticationController.setRedirectInSession(@req, '/js/something.js')
@AuthenticationController._setRedirectInSession(@req, '/js/something.js')
expect(@req.session.postLoginRedirect).to.equal undefined
describe '_getRedirectFromSession', ->

View file

@ -163,14 +163,12 @@ describe 'ProjectDetailsHandler', ->
describe "ensureProjectNameIsUnique", ->
beforeEach ->
@result = {
owned: [{_id: 1, name:"name"}, {_id: 2, name: "name1"}, {_id: 3, name: "name11"}, {_id: 100, name: "numeric"}]
owned: [{_id: 1, name:"name"}, {_id: 2, name: "name1"}, {_id: 3, name: "name11"}]
readAndWrite: [{_id: 4, name:"name2"}, {_id: 5, name:"name22"}]
readOnly: [{_id:6, name:"name3"}, {_id:7, name: "name33"}]
tokenReadAndWrite: [{_id:8, name:"name4"}, {_id:9, name:"name44"}]
tokenReadOnly: [{_id:10, name:"name5"}, {_id:11, name:"name55"}, {_id:12, name:"x".repeat(15)}]
}
for i in [1..20].concat([30..40])
@result.owned.push {_id: 100 + i, name: "numeric (#{i})"}
@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, @result)
it "should leave a unique name unchanged", (done) ->
@ -198,34 +196,9 @@ describe 'ProjectDetailsHandler', ->
expect(changed).to.equal true
done()
it "should use a numeric index if no suffix is supplied", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "name1", [], (error, name, changed) ->
expect(name).to.equal "name1 (1)"
expect(changed).to.equal true
done()
it "should use a numeric index if all suffixes are exhausted", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "name", ["1", "11"], (error, name, changed) ->
expect(name).to.equal "name (1)"
expect(changed).to.equal true
done()
it "should find the next lowest available numeric index for the base name", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "numeric", [], (error, name, changed) ->
expect(name).to.equal "numeric (21)"
expect(changed).to.equal true
done()
it "should find the next available numeric index when a numeric index is already present", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "numeric (5)", [], (error, name, changed) ->
expect(name).to.equal "numeric (21)"
expect(changed).to.equal true
done()
it "should not find a numeric index lower than the one already present", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "numeric (31)", [], (error, name, changed) ->
expect(name).to.equal "numeric (41)"
expect(changed).to.equal true
it "should return an error if the name cannot be made unique", (done) ->
@handler.ensureProjectNameIsUnique @user_id, "name", ["1", "5", "55"], (error, name, changed) ->
expect(error).to.eql new Errors.InvalidNameError("Project name could not be made unique")
done()
describe "fixProjectName", ->

View file

@ -79,7 +79,6 @@ describe "SubscriptionController", ->
"./RecurlyWrapper": @RecurlyWrapper = {}
"./FeaturesUpdater": @FeaturesUpdater = {}
"./GroupPlansData": @GroupPlansData = {}
"./V1SubscriptionManager": @V1SubscriptionManager = {}
@res = new MockResponse()

View file

@ -13,7 +13,7 @@ describe 'SudoModeMiddlewear', ->
isSudoModeActive: sinon.stub()
@AuthenticationController =
getLoggedInUserId: sinon.stub().returns(@userId)
setRedirectInSession: sinon.stub()
_setRedirectInSession: sinon.stub()
@SudoModeMiddlewear = SandboxedModule.require modulePath, requires:
'./SudoModeHandler': @SudoModeHandler
'../Authentication/AuthenticationController': @AuthenticationController
@ -54,7 +54,7 @@ describe 'SudoModeMiddlewear', ->
describe 'when sudo mode is not active', ->
beforeEach ->
@AuthenticationController.setRedirectInSession = sinon.stub()
@AuthenticationController._setRedirectInSession = sinon.stub()
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(@userId)
@SudoModeHandler.isSudoModeActive = sinon.stub().callsArgWith(1, null, false)
@ -71,8 +71,8 @@ describe 'SudoModeMiddlewear', ->
it 'should set redirect in session', (done) ->
@call () =>
@AuthenticationController.setRedirectInSession.callCount.should.equal 1
@AuthenticationController.setRedirectInSession.calledWith(@req).should.equal true
@AuthenticationController._setRedirectInSession.callCount.should.equal 1
@AuthenticationController._setRedirectInSession.calledWith(@req).should.equal true
done()
it 'should redirect to the password-prompt page', (done) ->

View file

@ -61,7 +61,7 @@ describe "TokenAccessController", ->
@TokenAccessHandler.addReadAndWriteUserToProject = sinon.stub()
.callsArgWith(2, null)
@ProjectController.loadEditor = sinon.stub()
@AuthenticationController.setRedirectInSession = sinon.stub()
@AuthenticationController._setRedirectInSession = sinon.stub()
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should try to find a project with this token', (done) ->
@ -173,7 +173,7 @@ describe "TokenAccessController", ->
.callsArgWith(2, null)
@ProjectController.loadEditor = sinon.stub()
@TokenAccessHandler.grantSessionTokenAccess = sinon.stub()
@AuthenticationController.setRedirectInSession = sinon.stub()
@AuthenticationController._setRedirectInSession = sinon.stub()
@TokenAccessController.readAndWriteToken @req, @res, @next
it 'should not add the user to the project with read-write access', (done) ->
@ -192,8 +192,8 @@ describe "TokenAccessController", ->
done()
it 'should set redirect in session', (done) ->
expect(@AuthenticationController.setRedirectInSession.callCount).to.equal 1
expect(@AuthenticationController.setRedirectInSession.calledWith(@req)).to.equal true
expect(@AuthenticationController._setRedirectInSession.callCount).to.equal 1
expect(@AuthenticationController._setRedirectInSession.calledWith(@req)).to.equal true
done()
it 'should redirect to restricted page', (done) ->

View file

@ -28,7 +28,7 @@ describe "UserPagesController", ->
getLoggedInUserId: sinon.stub().returns(@user._id)
getSessionUser: sinon.stub().returns(@user)
_getRedirectFromSession: sinon.stub()
setRedirectInSession: sinon.stub()
_setRedirectInSession: sinon.stub()
@UserPagesController = SandboxedModule.require modulePath, requires:
"settings-sharelatex": @settings
"logger-sharelatex":
@ -92,13 +92,13 @@ describe "UserPagesController", ->
beforeEach ->
@AuthenticationController._getRedirectFromSession = sinon.stub().returns(null)
@AuthenticationController.setRedirectInSession = sinon.stub()
@AuthenticationController._setRedirectInSession = sinon.stub()
@req.query.redir = '/somewhere/in/particular'
it 'should set a redirect', (done) ->
@res.render = (page) =>
@AuthenticationController.setRedirectInSession.callCount.should.equal 1
expect(@AuthenticationController.setRedirectInSession.lastCall.args[1]).to.equal @req.query.redir
@AuthenticationController._setRedirectInSession.callCount.should.equal 1
expect(@AuthenticationController._setRedirectInSession.lastCall.args[1]).to.equal @req.query.redir
done()
@UserPagesController.loginPage @req, @res

View file

@ -30,9 +30,10 @@ describe "UserMembershipAuthorization", ->
log: ->
err: ->
describe 'requireAccessToEntity', ->
describe 'requireEntityAccess', ->
it 'get entity', (done) ->
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
middlewear = @UserMembershipAuthorization.requireEntityAccess 'group'
middlewear @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@ -44,9 +45,19 @@ describe "UserMembershipAuthorization", ->
expect(@req.entityConfig).to.exist
done()
it 'handle unknown entity', (done) ->
middlewear = @UserMembershipAuthorization.requireEntityAccess 'foo'
middlewear @req, null, (error) =>
expect(error).to.extist
expect(error).to.be.an.instanceof(Errors.NotFoundError)
sinon.assert.notCalled(@UserMembershipHandler.getEntity)
expect(@req.entity).to.not.exist
done()
it 'handle entity not found', (done) ->
@UserMembershipHandler.getEntity.yields(null, null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
middlewear = @UserMembershipAuthorization.requireEntityAccess 'institution'
middlewear @req, null, (error) =>
expect(error).to.extist
sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
sinon.assert.called(@UserMembershipHandler.getEntity)
@ -55,63 +66,34 @@ describe "UserMembershipAuthorization", ->
it 'handle anonymous user', (done) ->
@AuthenticationController.getSessionUser.returns(null)
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
middlewear = @UserMembershipAuthorization.requireEntityAccess 'institution'
middlewear @req, null, (error) =>
expect(error).to.extist
sinon.assert.called(@AuthorizationMiddlewear.redirectToRestricted)
sinon.assert.notCalled(@UserMembershipHandler.getEntity)
expect(@req.entity).to.not.exist
done()
describe 'requireEntityAccess', ->
it 'handle team access', (done) ->
@UserMembershipAuthorization.requireTeamAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@req.params.id,
fields: primaryKey: 'overleaf.id'
)
done()
it 'handle group access', (done) ->
@UserMembershipAuthorization.requireGroupAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@req.params.id,
translations: title: 'group_account'
)
done()
it 'handle group managers access', (done) ->
@UserMembershipAuthorization.requireGroupManagersAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@req.params.id,
translations: subtitle: 'managers_management'
)
done()
it 'handle institution access', (done) ->
@UserMembershipAuthorization.requireInstitutionAccess @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@req.params.id,
modelName: 'Institution',
)
done()
it 'handle graph access', (done) ->
@req.query.resource_id = 'mock-resource-id'
@req.query.resource_type = 'institution'
middlewear = @UserMembershipAuthorization.requireGraphAccess
it 'can override entity id', (done) ->
middlewear = @UserMembershipAuthorization.requireEntityAccess 'group', 'entity-id-override'
middlewear @req, null, (error) =>
expect(error).to.not.extist
sinon.assert.calledWithMatch(
@UserMembershipHandler.getEntity,
@req.query.resource_id,
modelName: 'Institution',
'entity-id-override',
)
done()
it "doesn't cache entity id between requests", (done) ->
middlewear = @UserMembershipAuthorization.requireEntityAccess 'group'
middlewear @req, null, (error) =>
expect(error).to.not.extist
lastCallArs = @UserMembershipHandler.getEntity.lastCall.args
expect(lastCallArs[0]).to.equal @req.params.id
newEntityId = 'another-mock-id'
@req.params.id = newEntityId
middlewear @req, null, (error) =>
expect(error).to.not.extist
lastCallArs = @UserMembershipHandler.getEntity.lastCall.args
expect(lastCallArs[0]).to.equal newEntityId
done()

View file

@ -29,11 +29,6 @@ describe 'UserMembershipHandler', ->
v1Id: 123
managerIds: [ObjectId(), ObjectId(), ObjectId()]
update: sinon.stub().yields(null)
@publisher =
_id: 'mock-publisher-id'
slug: 'slug'
managerIds: [ObjectId(), ObjectId()]
update: sinon.stub().yields(null)
@UserMembershipViewModel =
buildAsync: sinon.stub().yields(null, { _id: 'mock-member-id'})
@ -44,15 +39,12 @@ describe 'UserMembershipHandler', ->
findOne: sinon.stub().yields(null, @institution)
@Subscription =
findOne: sinon.stub().yields(null, @subscription)
@Publisher =
findOne: sinon.stub().yields(null, @publisher)
@UserMembershipHandler = SandboxedModule.require modulePath, requires:
'./UserMembershipViewModel': @UserMembershipViewModel
'../User/UserGetter': @UserGetter
'../Errors/Errors': Errors
'../../models/Institution': Institution: @Institution
'../../models/Subscription': Subscription: @Subscription
'../../models/Publisher': Publisher: @Publisher
'logger-sharelatex':
log: ->
err: ->
@ -102,15 +94,6 @@ describe 'UserMembershipHandler', ->
expect(error).to.not.be.an.instanceof(Errors.NotFoundError)
done()
describe 'publishers', ->
it 'get publisher', (done) ->
@UserMembershipHandler.getEntity @publisher.slug, EntityConfigs.publisher, @user, (error, institution) =>
should.not.exist(error)
expectedQuery = slug: @publisher.slug, managerIds: ObjectId(@user._id)
assertCalledWith(@Publisher.findOne, expectedQuery)
expect(institution).to.equal @publisher
done()
describe 'getUsers', ->
describe 'group', ->
it 'build view model for all users', (done) ->