Question about Loc clashing rules

80 views
Skip to first unread message

hedefalk

unread,
Apr 6, 2011, 6:05:00 PM4/6/11
to Lift
If I have two different classes extending MegaProtoUsers: User and
Admin, and want to have sitemap entries for them both. Is this a bad
idea?

Thing is I want to separate them totally and have an admin(person) be
using the site both as admin and user with separate accounts.

Reason why I ask is the sitemap clashes I get, like:

net.liftweb.sitemap.SiteMapException: Location Login defined twice
Loc(Login, <function1>, LinkText(<function1>),
List(LocGroup(WrappedArray(customer)), If(<function0>,<function0>),
Template(<function0>))) and Loc(Login, <function1>,
LinkText(<function1>), List(If(<function0>,<function0>),
Template(<function0>)))
net.liftweb.sitemap.SiteMap.addLoc(SiteMap.scala:53)

At first I for some reason thought it was the path that was clashing I
so that it would easily be fixed by overriding basePath like:

override val basePath = "user" :: Nil
and
override val basePath = "admin" :: Nil
respectively.

but since neither that nor

SiteMap.enforceUniqueLinks = false

helped I looked again and realized it was the _name_ of the Loc that
was clashing. Since the only way I see how I could fix this is to
override all of the methods like:

def loginMenuLoc: Box[Menu] =
Full(Menu(Loc("Login", loginPath, S.??("login"),
loginMenuLocParams)))

to give new names to the Locs, I figured maybe I'm doing this all
wrong…? I feels like an obstacle I shouldn't climb over without
thinking twice basically. Or ask about :D

Another related question is the following: what is the name of a Loc
that is defined like:

Menu("Home") / "index" >> LocGroup("public")

hedefalk

unread,
Apr 6, 2011, 6:08:20 PM4/6/11
to Lift
Ah, I found the answer to my second question just seconds after
posting:

In Menu:
def apply(linkText: Loc.LinkText[Unit]): PreMenu =
this.apply(Helpers.randomString(20), linkText)

So it's random.

That strengthens my gut feeling that maybe I should not try to model
the users this way since it seems to be purposely hindered with global
unique ids like "Login" for the scaffolding Locs…?

David Pollak

unread,
Apr 6, 2011, 7:43:19 PM4/6/11
to lif...@googlegroups.com
On Wed, Apr 6, 2011 at 3:05 PM, hedefalk <hede...@gmail.com> wrote:
If I have two different classes extending MegaProtoUsers: User and
Admin, and want to have sitemap entries for them both. Is this a bad
idea?

I do not know of any systems that do that.  What about using the superuser field in MegaProtoUser to denote admin users vs. regular users?
 

Each Loc must have a unique name.  This allows you to get a named Loc via SiteMap.findLoc(name).  This is used in <lift:Menu.item name="Login"/>

You can define a menu that you're not going to access with Menu("What to display"), or if you want to explicitly access that menu item, Menu("MyMenu", "What to display")

 

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net

hedefalk

unread,
Apr 7, 2011, 7:50:19 AM4/7/11
to Lift
Thanks, now I understand. The name is for the menu-snippet, right.

> I do not know of any systems that do that.  What about using the superuser
> field in MegaProtoUser to denote admin users vs. regular users?

Yeah, that's how I done it before. Or added a roles-enum or something.
In the thing I'm building now, the functionality of admins and
customers are so separated, I first thought that I might build two
different webapps only communicating via a database. But then I
thought WTF and started with one instead.

Anyway, for some reason I really wanted to have separate classes/
tables for them and I'm now trying to follow that path at least for a
few more hours or until I strike a solid wall. Maybe I already have so
heres my next question:

I made a trait that overrides the Locs:

import mapper.{ MetaMegaProtoUser => LiftMetaMegaProtoUser }

trait MetaMegaProtoUser[ModelType <: MegaProtoUser[ModelType]] extends
LiftMetaMegaProtoUser[ModelType] {
self: ModelType =>

val menuNamePrefix: String

override def loginMenuLoc =
Full(Menu(Loc(menuNamePrefix + "Login", loginPath, S.??("login"),
loginMenuLocParams)))

...
override def changePasswordMenuLoc: Box[Menu] =
Full(Menu(Loc(menuNamePrefix + "ChangePassword",
changePasswordPath, S.??("change.password"),
changePasswordMenuLocParams)))
}

and had my Admin companion use this with

val menuNamePrefix = "Admin"

So now the Sitemap works fine and I can create both admin and user
accounts. BUT, the login state doesn't seem to be totally separated.
If I login as a user the menu for the admin appears as if I'm logged
in already. So there is something leaky about the login state of a
ProtoUser, am I right?

If I create an admin called "Admin Adminsson", log out, create a user
called "User Userson" and then click Edit User on the Admin menu, I
get a form populated with "Admin Adminsson" even though I actually
have logged out this admin.

I'm just trying to understand so please be patient with me.

If I follow the path of for instance the logoutMenuLocParams:

protected def logoutMenuLocParams: List[LocParam[Unit]] =
Template(() => wrapIt(logout)) ::
testLogginIn ::
Nil

-> testLogginIn -> .. ->
def loggedIn_? = {
if(!currentUserId.isDefined)
for(f <- autologinFunc) f()
currentUserId.isDefined
}

-> .. ->
private object curUserId extends SessionVar[Box[String]](Empty)


So the curUserId is a SessionVar on ProtoUser trait, but that should
be fine right? I mean it's not shared among subclasses of ProtoUser
since there's no statics in Scala…

So what is the reason that I cannot separate two login states in two
subclasses of ProtoUser?

LiftRules.loggedInTest doesn't have any thing to do with this, has it?

Cheers,
Viktor
> Beginning Scalahttp://www.apress.com/book/view/1430219890

hedefalk

unread,
Apr 7, 2011, 8:39:28 AM4/7/11
to Lift
I'll continue babbling a bit:

If I login as admin on a freshly restarted server and click user-
>edit_user, I get:

Message: java.lang.NullPointerException: Trying to open an empty Box
net.liftweb.common.EmptyBox.open_$bang(Box.scala:559)
net.liftweb.common.EmptyBox.open_$bang(Box.scala:544)
net.liftweb.proto.ProtoUser$class.edit(ProtoUser.scala:910)


from here:

def edit = {
val theUser: TheUserType =
mutateUserOnEdit(currentUser.open_!) // we know we're logged in



So curUserId is used to test if logged in by the LocParams of the
Menu. curUserId is a SessionVar:

private object curUserId extends SessionVar[Box[String]](Empty)

curUser on the other hand is used by the actual snippets and that is a
RequestVar:

private object curUser extends RequestVar[Box[TheUserType]]
(currentUserId.flatMap(userFromStringId)) with
CleanRequestVarOnSessionTransition

but that only means it is recalculated from curUserId on every
request, right? I just don't get it. How come the menu item is shown
but then the curUser is unset when trying to render?

David Pollak

unread,
Apr 7, 2011, 12:17:18 PM4/7/11
to lif...@googlegroups.com
The way we implement *Vars (RequestVar, SessionVar) is by calculating a "unique" name of the Var based on the Var's class name.  For the most part, it works.  For the most part, the class names for *Vars is unique.  There are some cases, however, where the class names are not unique even though the programmer sees two distinction instances of the *Var.  For this case, there's a way to add a unique salt to the *Var to guarantee uniqueness (we do this for RequestVars in Loc).  That mechanism must be done explicitly.

I've never considered the idea of having more than 1 ProtoUser in an app, but I guess it's not unreasonable.

Please open a ticket (http://ticket.liftweb.net you must be a watcher of the Assembla LiftWeb space to open tickets) and assign it to me referencing this thread.  I'll enhance ProtoUser to support having multiple ProtoUser objects in a single app.  Until that enhancement gets in, you're pretty much out of luck. :-(
Lift, the simply functional web framework http://liftweb.net

hedefalk

unread,
Apr 8, 2011, 7:03:02 AM4/8/11
to Lift
Ah, okay, I get it. So in both these subclasses the name for

private object curUserId extends SessionVar[Box[String]](Empty)

would still be net.liftweb.proto.ProtoUser$curUserId and then they
clash when this is used as a lookup-id in the session map.

Ticket opened here:
https://www.assembla.com/spaces/liftweb/tickets/962-make-it-possible-to-have-two-subclasses-of-protouser-with-separate-login-state-


On Apr 7, 6:17 pm, David Pollak <feeder.of.the.be...@gmail.com> wrote:
> The way we implement *Vars (RequestVar, SessionVar) is by calculating a
> "unique" name of the Var based on the Var's class name.  For the most part,
> it works.  For the most part, the class names for *Vars is unique.  There
> are some cases, however, where the class names are not unique even though
> the programmer sees two distinction instances of the *Var.  For this case,
> there's a way to add a unique salt to the *Var to guarantee uniqueness (we
> do this for RequestVars in Loc).  That mechanism must be done explicitly.
>
> I've never considered the idea of having more than 1 ProtoUser in an app,
> but I guess it's not unreasonable.
>
> Please open a ticket (http://ticket.liftweb.netyou must be a watcher of the

hedefalk

unread,
Apr 8, 2011, 7:09:50 AM4/8/11
to Lift
Actually, I still don't really get it. Maybe this is not the right
forum for me to learn basic Scala/Java though :D

If I look at AnyVarTrait I see the name declared like:

protected lazy val name = VarConstants.varPrefix+getClass.getName
+"_"+__nameSalt

Shouldn't getClass give the actual concrete runtime class? Like:


scala> trait Parent {def key =
getClass.getName }
defined trait Parent

scala> object Child extends Parent
defined module Child

scala> Child.key
res1: java.lang.String = Child$



On Apr 8, 1:03 pm, hedefalk <hedef...@gmail.com> wrote:
> Ah, okay, I get it. So in both these subclasses the name for
>
> private object curUserId extends SessionVar[Box[String]](Empty)
>
> would still be net.liftweb.proto.ProtoUser$curUserId and then they
> clash when this is used as a lookup-id in the session map.
>
> Ticket opened here:https://www.assembla.com/spaces/liftweb/tickets/962-make-it-possible-...
>
> On Apr 7, 6:17 pm, David Pollak <feeder.of.the.be...@gmail.com> wrote:
>
>
>
>
>
>
>
> > The way we implement *Vars (RequestVar, SessionVar) is by calculating a
> > "unique" name of the Var based on the Var's class name.  For the most part,
> > it works.  For the most part, the class names for *Vars is unique.  There
> > are some cases, however, where the class names are not unique even though
> > the programmer sees two distinction instances of the *Var.  For this case,
> > there's a way to add a unique salt to the *Var to guarantee uniqueness (we
> > do this for RequestVars in Loc).  That mechanism must be done explicitly.
>
> > I've never considered the idea of having more than 1 ProtoUser in an app,
> > but I guess it's not unreasonable.
>
> > Please open a ticket (http://ticket.liftweb.netyoumust be a watcher of the

hedefalk

unread,
Apr 8, 2011, 7:11:29 AM4/8/11
to Lift
Sorry, I'm really slow today. It's of course not at all the same as:

scala> trait Parent {object Var extends AnyRef { def key =
getClass.getName }}
defined trait Parent

scala> object Child extends Parent
defined module Child

scala> Child.Var.key
res0: java.lang.String = Parent$Var$

Sorry for spamming again.

Cheers,
Viktor
> > > Please open a ticket (http://ticket.liftweb.netyoumustbe a watcher of the
> > > > To post to this group, send email to...
>
> read more »

hedefalk

unread,
Apr 8, 2011, 9:17:44 AM4/8/11
to Lift
Maybe I'm taking this too far, but since I know that this will be
fixed sooner or later I thought I should hack around it for now and
clean up later rather than choosing the one-single-user-path.

So, for now I did this to my base trait:

// Lot of overrides to add salt to curUserId
// remove when fixed in ProtoUser
//
http://www.google.com/url?sa=D&q=https://www.assembla.com/spaces/liftweb/tickets/962-make-it-possible-to-have-two-subclasses-of-protouser-with-separate-login-state-

override val destroySessionOnLogin = false

override def loggedIn_? = {
if (!currentUserId.isDefined)
for (f <- autologinFunc) f()
currentUserId.isDefined
}

override def logUserIdIn(id: String) {
myCurUser.remove()
myCurUserId(Full(id))
}

override def logUserIn(who: TheUserType) {
myCurUserId.remove()
myCurUser.remove()
myCurUserId(Full(who.userIdAsString))
myCurUser(Full(who))
onLogIn.foreach(_(who))
}

override def logoutCurrentUser = logUserOut()

override def logUserOut() {
onLogOut.foreach(_(myCurUser))
myCurUserId.remove()
myCurUser.remove()
// Don't wanna kill session
// S.session.foreach(_.destroySession())
}

private object myCurUserId extends SessionVar[Box[String]](Empty) {
override protected def __nameSalt = varSalt
}

override def currentUserId: Box[String] = myCurUserId.is

private object myCurUser extends RequestVar[Box[TheUserType]]
(currentUserId.flatMap(userFromStringId)) with
CleanRequestVarOnSessionTransition {
override protected def __nameSalt = varSalt
}

protected def userFromStringId(id: String): Box[TheUserType]

override def currentUser: Box[TheUserType] = myCurUser.is


and gave concrete values to varSalt in my subclasses. It seems to be
working fine now. I can login users and admins independently. I'm only
worried about the actual LiftSession. Will the removal of:

S.session.foreach(_.destroySession())

and

override val destroySessionOnLogin = false


maybe create memory leaks?

Cheers,
Viktor
> > > > Please open a ticket (http://ticket.liftweb.netyoumustbea watcher of the
> > > > > > > > "Lift" group....
>
> read more »

David Pollak

unread,
Apr 8, 2011, 2:09:09 PM4/8/11
to lif...@googlegroups.com

No.  Those features were added for security purposes... changing the session cookie after login is security better practice.
 
>
> read more »

--

You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net

Viktor Hedefalk

unread,
May 7, 2011, 5:25:31 AM5/7/11
to lif...@googlegroups.com
From ticket:

David, I saw you fixed the salt on the vars in 2.4-M1. Great! I'm updating my code (trashing my hackarounds)

I'm thinking though, do we not also need to add something for the names of the Locs? They're defined as:


def loginMenuLoc: Box[Menu] =
Full(Menu(Loc("Login", loginPath, S.??("login"), loginMenuLocParams ::: globalUserLocParams)))

for instance and I think the names we're clashing for me before so I had to add something like


val menuNamePrefix: String

override def loginMenuLoc =
Full(Menu(Loc(menuNamePrefix + "Login", loginPath, S.??("login"), loginMenuLocParams ::: globalUserLocParams)))
.....
// + 7 more menuLoc overrides

to use the menus. I realize the title I wrote for the ticket doesn't cover that but If I could trash those too I would be even more happy :)

Cheers, Viktor

David Pollak

unread,
May 7, 2011, 7:36:42 AM5/7/11
to lif...@googlegroups.com
Yep.  Please open a new ticket on this.

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.



--
Lift, the simply functional web framework http://liftweb.net

Viktor Hedefalk

unread,
May 7, 2011, 4:55:32 PM5/7/11
to lif...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages