Skip to content

Commit 25fe332

Browse files
authored
Merge pull request #242 from EclecticIQ/logind
login1: add support for session management
2 parents 40e2722 + e1da176 commit 25fe332

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

@@ -85,6 +85,147 @@ func (c *Conn) initConnection() error {
8585
return nil
8686
}
8787

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