Skip to content

Commit e1da176

Browse files
committed
login1: add support for session management
1 parent c966a8a commit e1da176

File tree

2 files changed

+204
-2
lines changed

2 files changed

+204
-2
lines changed

login1/dbus.go

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
// Integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
15+
// Package login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/
1616
package login1
1717

1818
import (
@@ -34,7 +34,7 @@ type Conn struct {
3434
object dbus.BusObject
3535
}
3636

37-
// New() establishes a connection to the system bus and authenticates.
37+
// New establishes a connection to the system bus and authenticates.
3838
func New() (*Conn, error) {
3939
c := new(Conn)
4040

@@ -74,6 +74,147 @@ func (c *Conn) initConnection() error {
7474
return nil
7575
}
7676

77+
// Session object definition.
78+
type Session struct {
79+
ID string
80+
UID uint32
81+
User string
82+
Seat string
83+
Path dbus.ObjectPath
84+
}
85+
86+
// User object definition.
87+
type User struct {
88+
UID uint32
89+
Name string
90+
Path dbus.ObjectPath
91+
}
92+
93+
func (s Session) toInterface() []interface{} {
94+
return []interface{}{s.ID, s.UID, s.User, s.Seat, s.Path}
95+
}
96+
97+
func sessionFromInterfaces(session []interface{}) (*Session, error) {
98+
if len(session) < 5 {
99+
return nil, fmt.Errorf("invalid number of session fields: %d", len(session))
100+
}
101+
id, ok := session[0].(string)
102+
if !ok {
103+
return nil, fmt.Errorf("failed to typecast session field 0 to string")
104+
}
105+
uid, ok := session[1].(uint32)
106+
if !ok {
107+
return nil, fmt.Errorf("failed to typecast session field 1 to uint32")
108+
}
109+
user, ok := session[2].(string)
110+
if !ok {
111+
return nil, fmt.Errorf("failed to typecast session field 2 to string")
112+
}
113+
seat, ok := session[3].(string)
114+
if !ok {
115+
return nil, fmt.Errorf("failed to typecast session field 2 to string")
116+
}
117+
path, ok := session[4].(dbus.ObjectPath)
118+
if !ok {
119+
return nil, fmt.Errorf("failed to typecast session field 4 to ObjectPath")
120+
}
121+
122+
ret := Session{ID: id, UID: uid, User: user, Seat: seat, Path: path}
123+
return &ret, nil
124+
}
125+
126+
func userFromInterfaces(user []interface{}) (*User, error) {
127+
if len(user) < 3 {
128+
return nil, fmt.Errorf("invalid number of user fields: %d", len(user))
129+
}
130+
uid, ok := user[0].(uint32)
131+
if !ok {
132+
return nil, fmt.Errorf("failed to typecast user field 0 to uint32")
133+
}
134+
name, ok := user[1].(string)
135+
if !ok {
136+
return nil, fmt.Errorf("failed to typecast session field 1 to string")
137+
}
138+
path, ok := user[2].(dbus.ObjectPath)
139+
if !ok {
140+
return nil, fmt.Errorf("failed to typecast user field 2 to ObjectPath")
141+
}
142+
143+
ret := User{UID: uid, Name: name, Path: path}
144+
return &ret, nil
145+
}
146+
147+
// GetSession may be used to get the session object path for the session with the specified ID.
148+
func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) {
149+
var out interface{}
150+
if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil {
151+
return "", err
152+
}
153+
154+
ret, ok := out.(dbus.ObjectPath)
155+
if !ok {
156+
return "", fmt.Errorf("failed to typecast session to ObjectPath")
157+
}
158+
159+
return ret, nil
160+
}
161+
162+
// ListSessions returns an array with all current sessions.
163+
func (c *Conn) ListSessions() ([]Session, error) {
164+
out := [][]interface{}{}
165+
if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil {
166+
return nil, err
167+
}
168+
169+
ret := []Session{}
170+
for _, el := range out {
171+
session, err := sessionFromInterfaces(el)
172+
if err != nil {
173+
return nil, err
174+
}
175+
ret = append(ret, *session)
176+
}
177+
return ret, nil
178+
}
179+
180+
// ListUsers returns an array with all currently logged in users.
181+
func (c *Conn) ListUsers() ([]User, error) {
182+
out := [][]interface{}{}
183+
if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil {
184+
return nil, err
185+
}
186+
187+
ret := []User{}
188+
for _, el := range out {
189+
user, err := userFromInterfaces(el)
190+
if err != nil {
191+
return nil, err
192+
}
193+
ret = append(ret, *user)
194+
}
195+
return ret, nil
196+
}
197+
198+
// LockSession asks the session with the specified ID to activate the screen lock.
199+
func (c *Conn) LockSession(id string) {
200+
c.object.Call(dbusInterface+".LockSession", 0, id)
201+
}
202+
203+
// LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action.
204+
func (c *Conn) LockSessions() {
205+
c.object.Call(dbusInterface+".LockSessions", 0)
206+
}
207+
208+
// TerminateSession forcibly terminate one specific session.
209+
func (c *Conn) TerminateSession(id string) {
210+
c.object.Call(dbusInterface+".TerminateSession", 0, id)
211+
}
212+
213+
// TerminateUser forcibly terminates all processes of a user.
214+
func (c *Conn) TerminateUser(uid uint32) {
215+
c.object.Call(dbusInterface+".TerminateUser", 0, uid)
216+
}
217+
77218
// Reboot asks logind for a reboot optionally asking for auth.
78219
func (c *Conn) Reboot(askForAuth bool) {
79220
c.object.Call(dbusInterface+".Reboot", 0, askForAuth)

login1/dbus_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
package login1
1616

1717
import (
18+
"fmt"
19+
"os/user"
20+
"regexp"
1821
"testing"
1922
)
2023

@@ -26,3 +29,61 @@ func TestNew(t *testing.T) {
2629
t.Fatal(err)
2730
}
2831
}
32+
33+
func TestListSessions(t *testing.T) {
34+
c, err := New()
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
sessions, err := c.ListSessions()
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
if len(sessions) > 0 {
45+
for _, s := range sessions {
46+
lookup, err := user.Lookup(s.User)
47+
if err != nil {
48+
t.Fatal(err)
49+
}
50+
if fmt.Sprint(s.UID) != lookup.Uid {
51+
t.Fatalf("expected uid '%d' but got '%s'", s.UID, lookup.Uid)
52+
}
53+
54+
validPath := regexp.MustCompile(`/org/freedesktop/login1/session/_[0-9]+`)
55+
if !validPath.MatchString(fmt.Sprint(s.Path)) {
56+
t.Fatalf("invalid session path: %s", s.Path)
57+
}
58+
}
59+
}
60+
}
61+
62+
func TestListUsers(t *testing.T) {
63+
c, err := New()
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
68+
users, err := c.ListUsers()
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
if len(users) > 0 {
74+
for _, u := range users {
75+
lookup, err := user.Lookup(u.Name)
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
if fmt.Sprint(u.UID) != lookup.Uid {
80+
t.Fatalf("expected uid '%d' but got '%s'", u.UID, lookup.Uid)
81+
}
82+
83+
validPath := regexp.MustCompile(`/org/freedesktop/login1/user/_[0-9]+`)
84+
if !validPath.MatchString(fmt.Sprint(u.Path)) {
85+
t.Fatalf("invalid user path: %s", u.Path)
86+
}
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)