0

I have read all the stack overflow questions related to the issue. I got some idea about it but it could not help me to solve this issue.

I have used service account for authorization. Because I don't want to display the authorization screen for my users. So I have created service account and enabled domain-wide delegation authority in GoogleAPI console.

I need to create courses in the user's google classroom in a programmatical way. I have done using OAuth client ID and OAuth Client secret by following the java Quickstart. After completing the authorization token has been stored in the project directory. Using this token I can create courses in the authorized user's google classroom. Since I need to create courses in google classroom for many users in my domain. So I used service account. When I tried to implement it, I got the above error.

I have implemented it in the web application. For better understanding and code readability, I created a quickstart maven project and pasted the code. The code is working fine up to the System.out.println("Entering to list course method: "); line.

1) How to solve this issue?

2) Will it ask for authorization of Admin? Because in order to access the users data, we need access token. How can I get access token using service account?

public class ClassroomServiceAccount {

    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    private static final String SERVICE_ACCOUNT_EMAIL = "xxxxxxx@projectName.iam.gserviceaccount.com";
    private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/MyProject.p12";
    private static final List<String> SCOPES = Arrays.asList(ClassroomScopes.CLASSROOM_COURSES, ClassroomScopes.CLASSROOM_TOPICS, ClassroomScopes.CLASSROOM_ANNOUNCEMENTS);

    public static void main(String... args) throws IOException, GeneralSecurityException {

        HttpTransport httpTransport = new NetHttpTransport();
        InputStream in = ClassroomServiceAccount.class.getResourceAsStream(SERVICE_ACCOUNT_PKCS12_FILE_PATH);
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(in, "notasecret".toCharArray());

        String alias = "key1";

        Key key = keystore.getKey(alias, "notasecret".toCharArray());

          // Get certificate of public key
          Certificate cert = keystore.getCertificate(alias);

          // Get public key
          PublicKey publicKey = cert.getPublicKey();
          System.out.println("This is public key : "+ publicKey);

          // Return a key pair
          KeyPair k = new KeyPair(publicKey, (PrivateKey) key);
          System.out.println("This is k : "+ k.getPrivate());



    GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                .setServiceAccountPrivateKey(k.getPrivate())
                .setServiceAccountScopes(SCOPES)
                .setServiceAccountUser("email@yourdomain.com")
                .build();   


  System.out.println("credentials : " + credential.getServiceAccountPrivateKey());

  Classroom service = new Classroom.Builder(httpTransport, JSON_FACTORY, null)
        .setApplicationName(APPLICATION_NAME)
        .setHttpRequestInitializer(credential)
        .build();

        // Create courses in users google classroom
        Course course = new Course();
        course.setOwnerId("user@yourdomain.com");
        course.setName("Science");
        service.courses().create(course).execute();
        }
    }
Satheeshkumar
  • 75
  • 3
  • 12
  • Did you create the `key` from the Service Account? The token is in there. – Jescanellas Dec 04 '19 at 11:59
  • Yes. I have downloaded the .p12 key. You can see it in my code. i.e., private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/MyProject.p12"; – Satheeshkumar Dec 04 '19 at 12:02
  • @Jescanellas I got the private key from .p12 file and pass it in the credential (setServiceAccountPrivateKey). I have referred this answer https://stackoverflow.com/questions/18621508/getting-a-privatekey-object-from-a-p12-file-in-java/41954406#41954406 – Satheeshkumar Dec 04 '19 at 12:08
  • I see. Do not use that post as it is from 2013 and it's outdated. Try replicating the Step 3 authorization from the same [Quickstart](https://developers.google.com/classroom/quickstart/java#step_3_set_up_the_sample). – Jescanellas Dec 04 '19 at 13:51
  • @Jescanellas I can create course using that Quickstart. They used OAuth client ID. But my question is different. I would like to use service account since it enables server-to-server, app level authentication using robot accounts. – Satheeshkumar Dec 05 '19 at 04:04
  • I know, I explained myself better in an answer :) – Jescanellas Dec 09 '19 at 14:00

2 Answers2

0

As talked in the comments, you have to replace the user credentials with the ones from your Service Account. In this example I modified the credential's Classroom Quickstart example with the ones from the Service Account. As you see I completely removed the getCredentials function. However you can put the credentials builder there anyway:

public class ClassroomQuickstart {
    private static final String APPLICATION_NAME = "Google Classroom API Java Quickstart";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    private static final String TOKENS_DIRECTORY_PATH = "tokens";
    private static final String KEY_FILE_LOCATION = "full path of your service account creds json";

    /**
     * Global instance of the scopes required by this quickstart.
     * If modifying these scopes, delete your previously saved tokens/ folder.
     */
    private static final List<String> SCOPES = Collections.singletonList(ClassroomScopes.CLASSROOM_COURSES_READONLY);


    /**
     * Creates an authorized Credential object.
     * @param HTTP_TRANSPORT The network HTTP Transport.
     * @return An authorized Credential object.
     * @throws IOException If the credentials.json file cannot be found.
     */

    public static void main(String... args) throws IOException, GeneralSecurityException {


        GoogleCredential credential = GoogleCredential
                .fromStream(new FileInputStream(KEY_FILE_LOCATION))
                .createScoped(ClassroomScopes.all());
        // Build a new authorized API client service.
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        Classroom service = new Classroom.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
                .setApplicationName(APPLICATION_NAME)
                .build();

        // List the first 10 courses that the user has access to.
        ListCoursesResponse response = service.courses().list()
                .setPageSize(10)
                .execute();
        List<Course> courses = response.getCourses();
        if (courses == null || courses.size() == 0) {
            System.out.println("No courses found.");
        } else {
            System.out.println("Courses:");
            for (Course course : courses) {
                System.out.printf("%s\n", course.getName());
            }
        }
    }
}


Update: Domain delegation

To create a Course in your Google account using a Service Account you need to impersonate it. To achieve this you have to perform the so called Domain-Wide Delegation.

As you already created the Service Account, I'm jumping to the domain-wide delegation authorization:

  1. Go to your GCP and select the Service Account for your project.
  2. Click on the left three vertical dots and then Edit
  3. In the service account details, click Show domain-wide delegation, then ensure the Enable G Suite Domain-wide Delegation checkbox is checked.
  4. Click Save to update the service account, and return to the table of service accounts. A new column, Domain-wide delegation, can be seen. Click View Client ID, to obtain and make a note of the client ID.

Now you have to authorize your user so your Service Account can impersonate it. I'm not writing the steps as it would be too long, but you can see them here.

Back to the code, we have to choose the way the credentials are set. To keep things more simple, I'm now using a P12 Key File instead of the JSON credentials:

GoogleCredential credential2 = new GoogleCredential.Builder()
                .setTransport(HTTP_TRANSPORT)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId("example@project.iam.gserviceaccount.com")
                .setServiceAccountScopes(SCOPES)
                .setServiceAccountUser("my_email@example.com")
                .setServiceAccountPrivateKeyFromP12File(new File(KEY_FILE_LOCATION)).build();

Rest of the Domain-wide Quickstart here .

Your Classroom service builder and the new Course code is correct and should work with the new credentials.

Jescanellas
  • 2,555
  • 2
  • 9
  • 20
  • Thank you for your answer. Can you clear me two doubts? 1) I need to create course/class in my user's google classroom. Where I can specify their email ID/Owner Id? 2) When I execute this code, I got "the system cannot fine the specified path". I have given qualified name for path (projectname/src/main/resources). How to solve this? – Satheeshkumar Dec 09 '19 at 14:30
  • 1
    1) Are you creating the Course from the API or from the Web interface? 2) Regarding the path location, you have to use the full path, as it is using a FileInputStream. For example: `/usr/local/bin/home/userName/myProjects/java/IntellijProjects/src/main/resources/service_creds.json` – Jescanellas Dec 09 '19 at 14:50
  • 1) Yes, I am going to create course using a web interface. – Satheeshkumar Dec 09 '19 at 15:51
  • 2) Thank you for your wonderful help. Now it displays "No course found". Can you tell me how to create courses in specified user's google classroom account? – Satheeshkumar Dec 09 '19 at 16:21
  • Sure! Take a look at this [guide](https://support.google.com/edu/classroom/answer/6020273?co=GENIE.Platform%3DDesktop&hl=en) showing the steps to do it. – Jescanellas Dec 09 '19 at 16:26
  • Also, if the answer was helpful to you I would appreciate if you could accept it, so others can see the Credential issue was solved without reading the comments :) – Jescanellas Dec 09 '19 at 16:57
  • I should create courses in specified user's google classroom account using API (not on web interface). Actually I have a web form in my application. If I submit the form, it will use the parameters like email & course name and it should create the course in specified user's google classroom. But can you tell me how to course course using API? – Satheeshkumar Dec 09 '19 at 17:08
  • You can see in my code, I have mentioned the user's email "user@yourdomain.com". In the above code, I have tried to create a course in the particular user's google classroom. How to do that with your code? One more steps to go. Your answer is very helpful for me. Thanks in advance – Satheeshkumar Dec 09 '19 at 17:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203972/discussion-between-jescanellas-and-satheeshkumar). – Jescanellas Dec 10 '19 at 10:27
0

I have solved this exception by changing the scopes. i.e., In my code, I have used the three scopes. Those three scopes should be matched with Gsuite's scope. While delegating domain-wide authority to the service account, we added scope. Both these scopes should be matched in order to avoid 401 unauthorized.

Satheeshkumar
  • 75
  • 3
  • 12